Skip to content

Commit 7981956

Browse files
authored
feat(carreirs): implement environment context getter setter (#1668)
* feat(carreirs): implement environment context getter setter Closes #1566 * tests(carrier): add EnvironmentGetterSetter missing test * fix(carrier): modify keys empty env condition * test: use OpenTelemetry\Tests\TestState trait to handle environment * docs: add trace context and baggage environment carrier example * refactor: clean up baggage and trace context examples by removing unused span logic * test: fix Psalm static analysis failures
1 parent 9d68afb commit 7981956

File tree

4 files changed

+289
-0
lines changed

4 files changed

+289
-0
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Example;
6+
7+
require __DIR__ . '/../../vendor/autoload.php';
8+
9+
use OpenTelemetry\API\Baggage\Baggage;
10+
use OpenTelemetry\API\Baggage\Propagation\BaggagePropagator;
11+
use OpenTelemetry\Context\Propagation\EnvironmentGetterSetter;
12+
13+
$propagator = BaggagePropagator::getInstance();
14+
$envGetterSetter = EnvironmentGetterSetter::getInstance();
15+
16+
$isChild = isset($argv[1]) && $argv[1] === 'child';
17+
18+
if (!$isChild) {
19+
$baggage = Baggage::getBuilder()
20+
->set('key1', 'value1')
21+
->set('key2', 'value2')
22+
->build();
23+
$baggageScope = $baggage->activate();
24+
25+
// Inject baggage into environment variables
26+
$carrier = [];
27+
$propagator->inject($carrier, $envGetterSetter);
28+
29+
// Execute child process
30+
$command = sprintf('%s %s %s', PHP_BINARY, escapeshellarg(__FILE__), 'child');
31+
$handle = popen($command, 'w');
32+
if ($handle === false) {
33+
echo 'Failed to execute child process.' . PHP_EOL;
34+
} else {
35+
pclose($handle);
36+
}
37+
38+
$baggageScope->detach();
39+
} else {
40+
// Extract baggage from environment variables
41+
$context = $propagator->extract([], $envGetterSetter);
42+
$scope = $context->activate();
43+
44+
// Get values from baggage
45+
$baggage = Baggage::getCurrent();
46+
echo 'key1: ' . $baggage->getValue('key1') . PHP_EOL;
47+
echo 'key2: ' . $baggage->getValue('key2') . PHP_EOL;
48+
49+
$scope->detach();
50+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require 'vendor/autoload.php';
6+
7+
use OpenTelemetry\API\Trace\Propagation\TraceContextPropagator;
8+
use OpenTelemetry\Context\Propagation\EnvironmentGetterSetter;
9+
use OpenTelemetry\SDK\Trace\SpanExporter\ConsoleSpanExporterFactory;
10+
use OpenTelemetry\SDK\Trace\SpanProcessor\SimpleSpanProcessor;
11+
use OpenTelemetry\SDK\Trace\TracerProvider;
12+
13+
$tracerProvider = new TracerProvider(
14+
new SimpleSpanProcessor(
15+
(new ConsoleSpanExporterFactory())->create()
16+
)
17+
);
18+
$tracer = $tracerProvider->getTracer('io.opentelemetry.contrib.php');
19+
20+
$propagator = TraceContextPropagator::getInstance();
21+
$envGetterSetter = EnvironmentGetterSetter::getInstance();
22+
23+
$isChild = isset($argv[1]) && $argv[1] === 'child';
24+
25+
if (!$isChild) {
26+
$span = $tracer->spanBuilder('root')->startSpan();
27+
$scope = $span->activate();
28+
29+
// Inject trace context into environment variables
30+
$carrier = [];
31+
$propagator->inject($carrier, $envGetterSetter);
32+
33+
// Execute child process
34+
$command = sprintf('%s %s %s', PHP_BINARY, escapeshellarg(__FILE__), 'child');
35+
$handle = popen($command, 'w');
36+
if ($handle === false) {
37+
echo 'Failed to execute child process.' . PHP_EOL;
38+
} else {
39+
pclose($handle);
40+
}
41+
42+
$scope->detach();
43+
$span->end();
44+
} else {
45+
// Extract trace context from environment variables
46+
$context = $propagator->extract([], $envGetterSetter);
47+
$scope = $context->activate();
48+
49+
// Start child span with parent context
50+
$span = $tracer->spanBuilder('child-span')->setParent($context)->startSpan();
51+
$spanContext = $span->getContext();
52+
53+
$scope->detach();
54+
$span->end();
55+
}
56+
57+
$tracerProvider->shutdown();
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Context\Propagation;
6+
7+
use InvalidArgumentException;
8+
9+
/**
10+
* @see https://github.com/open-telemetry/opentelemetry-specification/blob/v1.44.0/specification/context/env-carriers.md
11+
*
12+
* Default implementation of {@see ExtendedPropagationGetterInterface} and {@see PropagationSetterInterface}.
13+
* This type uses environment variables as a carrier for context propagation.
14+
* It is provided to {@see TextMapPropagatorInterface::inject()} or {@see TextMapPropagatorInterface::extract()}.
15+
*/
16+
final class EnvironmentGetterSetter implements ExtendedPropagationGetterInterface, PropagationSetterInterface
17+
{
18+
private static ?self $instance = null;
19+
20+
public static function getInstance(): self
21+
{
22+
return self::$instance ??= new self();
23+
}
24+
25+
#[\Override]
26+
public function keys($carrier): array
27+
{
28+
$envs = getenv();
29+
if (!is_array($envs) || $envs === []) {
30+
return [];
31+
}
32+
33+
return array_map('strtolower', array_keys($envs));
34+
}
35+
36+
#[\Override]
37+
public function get($carrier, string $key): ?string
38+
{
39+
$value = getenv(strtoupper($key));
40+
41+
return is_string($value) ? $value : null;
42+
}
43+
44+
#[\Override]
45+
public function getAll($carrier, string $key): array
46+
{
47+
$value = getenv(strtoupper($key));
48+
49+
return is_string($value) ? [$value] : [];
50+
}
51+
52+
#[\Override]
53+
public function set(&$carrier, string $key, string $value): void
54+
{
55+
if ($key === '') {
56+
throw new InvalidArgumentException('Unable to set value with an empty key');
57+
}
58+
59+
putenv(sprintf('%s=%s', strtoupper($key), $value));
60+
}
61+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\Tests\Unit\Context\Propagation;
6+
7+
use InvalidArgumentException;
8+
use OpenTelemetry\Context\Propagation\EnvironmentGetterSetter;
9+
use OpenTelemetry\Tests\TestState;
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
use PHPUnit\Framework\TestCase;
12+
13+
#[CoversClass(EnvironmentGetterSetter::class)]
14+
class EnvironmentGetterSetterTest extends TestCase
15+
{
16+
use TestState;
17+
18+
#[\Override]
19+
protected function tearDown(): void
20+
{
21+
$this->restoreEnvironmentVariables();
22+
}
23+
24+
public function test_get_instance(): void
25+
{
26+
$instance = EnvironmentGetterSetter::getInstance();
27+
28+
$this->assertInstanceOf(EnvironmentGetterSetter::class, $instance);
29+
}
30+
31+
public function test_keys_from_environment(): void
32+
{
33+
$carrier = [];
34+
$this->setEnvironmentVariable('A', 'alpha');
35+
$this->setEnvironmentVariable('B', 'beta=bravo');
36+
37+
$map = new EnvironmentGetterSetter();
38+
$keys = $map->keys($carrier);
39+
$this->assertContains('a', $keys);
40+
$this->assertContains('b', $keys);
41+
}
42+
43+
public function test_keys_with_empty_environment(): void
44+
{
45+
foreach (array_keys(getenv()) as $key) {
46+
$this->setEnvironmentVariable($key, null);
47+
}
48+
$map = new EnvironmentGetterSetter();
49+
50+
$this->assertSame([], $map->keys([]));
51+
}
52+
53+
public function test_get_values_from_environment(): void
54+
{
55+
$this->setEnvironmentVariable('A', 'alpha');
56+
$this->setEnvironmentVariable('B', 'beta');
57+
$map = new EnvironmentGetterSetter();
58+
59+
$this->assertSame('alpha', $map->get([], 'a'));
60+
$this->assertSame('beta', $map->get([], 'b'));
61+
}
62+
63+
public function test_for_empty_key(): void
64+
{
65+
$map = new EnvironmentGetterSetter();
66+
67+
$this->assertNull($map->get([], ''));
68+
}
69+
70+
public function test_for_get_with_nonexistent_key(): void
71+
{
72+
$map = new EnvironmentGetterSetter();
73+
74+
$this->assertNull($map->get([], 'not_exist'));
75+
}
76+
77+
public function test_can_get_integer_value(): void
78+
{
79+
$this->setEnvironmentVariable('A', '1');
80+
$map = new EnvironmentGetterSetter();
81+
82+
$this->assertSame('1', $map->get([], 'a'));
83+
}
84+
85+
public function test_can_get_all_values_from_environment(): void
86+
{
87+
$this->setEnvironmentVariable('A', 'alpha');
88+
$this->setEnvironmentVariable('B', 'beta');
89+
$map = new EnvironmentGetterSetter();
90+
91+
$this->assertSame(['alpha'], $map->getAll([], 'a'));
92+
$this->assertSame(['beta'], $map->getAll([], 'b'));
93+
}
94+
95+
public function test_for_get_all_with_nonexistent_key(): void
96+
{
97+
$map = new EnvironmentGetterSetter();
98+
99+
$this->assertSame([], $map->getAll([], 'not_exist'));
100+
}
101+
102+
public function test_set_environment(): void
103+
{
104+
$carrier = [];
105+
$map = new EnvironmentGetterSetter();
106+
107+
$map->set($carrier, 'b', 'beta');
108+
$this->assertSame('beta', $map->get($carrier, 'b'));
109+
$this->assertSame('beta', getenv('B'));
110+
}
111+
112+
public function test_set_empty_key(): void
113+
{
114+
$carrier = [];
115+
$map = new EnvironmentGetterSetter();
116+
117+
$this->expectException(InvalidArgumentException::class);
118+
$this->expectExceptionMessage('Unable to set value with an empty key');
119+
$map->set($carrier, '', 'alpha');
120+
}
121+
}

0 commit comments

Comments
 (0)