diff --git a/composer.json b/composer.json index 5d8cb12c6..1da93ceb3 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,8 @@ "psr-4": { "OpenTelemetry\\Trace\\": "api/Trace", "OpenTelemetry\\Sdk\\Internal\\": "sdk/Internal", - "OpenTelemetry\\Sdk\\Trace\\": "sdk/Trace" + "OpenTelemetry\\Sdk\\Trace\\": "sdk/Trace", + "OpenTelemetry\\Sdk\\Resource\\": "sdk/Resource" } }, "autoload-dev": { diff --git a/sdk/Resource/ResourceConstants.php b/sdk/Resource/ResourceConstants.php new file mode 100644 index 000000000..6e0ffda2e --- /dev/null +++ b/sdk/Resource/ResourceConstants.php @@ -0,0 +1,73 @@ +attributes = $attributes; + } + + public static function create(Attributes $attributes): self + { + return new ResourceInfo(clone $attributes); + } + + /** + * Merges two resources into a new one. + * Conflicts (i.e. a key for which attributes exist on both the primary and secondary resource) are handled as follows: + * - If the value on the primary resource is an empty string, the result has the value of the secondary resource. + * - Otherwise, the value of the primary resource is used. + * + * @param ResourceInfo $primary + * @param ResourceInfo $secondary + * @return ResourceInfo + */ + public static function merge(ResourceInfo $primary, ResourceInfo $secondary): self + { + // clone attributes from the primary resource + $mergedAttributes = clone $primary->getAttributes(); + + // merge attributes from the secondary resource + foreach ($secondary->getAttributes() as $name => $attribute) { + if (null === $mergedAttributes->getAttribute($name) || $mergedAttributes->getAttribute($name)->getValue() === '') { + $mergedAttributes->setAttribute($name, $attribute->getValue()); + } + } + + return new ResourceInfo($mergedAttributes); + } + + public static function emptyResource(): self + { + return new ResourceInfo(new Attributes()); + } + + public function getAttributes(): Attributes + { + return $this->attributes; + } +} diff --git a/sdk/Trace/InstrumentationLibrary.php b/sdk/Trace/InstrumentationLibrary.php new file mode 100644 index 000000000..63292c37b --- /dev/null +++ b/sdk/Trace/InstrumentationLibrary.php @@ -0,0 +1,34 @@ +name = $name; + $this->version = $version; + } + + /** + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * @return string|null + */ + public function getVersion(): ?string + { + return $this->version; + } +} diff --git a/sdk/Trace/Tracer.php b/sdk/Trace/Tracer.php index a4b09b3a4..908d5cd97 100644 --- a/sdk/Trace/Tracer.php +++ b/sdk/Trace/Tracer.php @@ -17,10 +17,18 @@ class Tracer implements API\Tracer */ private $provider; - public function __construct(TracerProvider $provider, API\SpanContext $context = null) - { - $this->provider = $provider; + /** + * @var InstrumentationLibrary + */ + private $instrumentationLibrary; + public function __construct( + TracerProvider $provider, + InstrumentationLibrary $instrumentationLibrary, + API\SpanContext $context = null + ) { + $this->provider = $provider; + $this->instrumentationLibrary = $instrumentationLibrary; $context = $context ?: SpanContext::generate(); // todo: hold up, why do we automatically make a root Span? diff --git a/sdk/Trace/TracerProvider.php b/sdk/Trace/TracerProvider.php index 00174ba27..3b2649e4a 100644 --- a/sdk/Trace/TracerProvider.php +++ b/sdk/Trace/TracerProvider.php @@ -4,6 +4,7 @@ namespace OpenTelemetry\Sdk\Trace; +use OpenTelemetry\Sdk\Resource\ResourceInfo; use OpenTelemetry\Trace as API; final class TracerProvider implements API\TracerProvider @@ -19,9 +20,15 @@ final class TracerProvider implements API\TracerProvider */ protected $spanProcessors; - public function __construct() + /** + * @var ResourceInfo + */ + private $resource; + + public function __construct(?ResourceInfo $resource = null) { $this->spanProcessors = new SpanMultiProcessor(); + $this->resource = $resource ?? ResourceInfo::emptyResource(); } public function getTracer(string $name, ?string $version = ''): API\Tracer @@ -31,8 +38,9 @@ public function getTracer(string $name, ?string $version = ''): API\Tracer } $spanContext = SpanContext::generate(); + $instrumentationLibrary = new InstrumentationLibrary($name, $version); - return $this->tracers[$name] = new Tracer($this, $spanContext); + return $this->tracers[$name] = new Tracer($this, $instrumentationLibrary, $spanContext); } public function addSpanProcessor(SpanProcessor $processor): self @@ -46,4 +54,9 @@ public function getSpanProcessor(): SpanMultiProcessor { return $this->spanProcessors; } + + public function getResource(): ResourceInfo + { + return $this->resource; + } } diff --git a/tests/unit/Resource/ResourceTest.php b/tests/unit/Resource/ResourceTest.php new file mode 100644 index 000000000..4d7bf7dca --- /dev/null +++ b/tests/unit/Resource/ResourceTest.php @@ -0,0 +1,56 @@ +assertEmpty($resource->getAttributes()); + } + + public function testGetAttributes() + { + $attributes = new Attributes(); + $attributes->setAttribute('name', 'test'); + $resource = ResourceInfo::create($attributes); + + $this->assertEquals($attributes, $resource->getAttributes()); + $this->assertEquals('test', $resource->getAttributes()->getAttribute('name')->getValue()); + } + + public function testMerge() + { + $primary = ResourceInfo::create(new Attributes(['name' => 'primary', 'empty' => ''])); + $secondary = ResourceInfo::create(new Attributes(['version' => '1.0.0', 'empty' => 'value'])); + $result = ResourceInfo::merge($primary, $secondary); + + $this->assertCount(3, $result->getAttributes()); + $this->assertEquals('primary', $result->getAttributes()->getAttribute('name')->getValue()); + $this->assertEquals('1.0.0', $result->getAttributes()->getAttribute('version')->getValue()); + $this->assertEquals('value', $result->getAttributes()->getAttribute('empty')->getValue()); + } + + public function testImmutableCreate() + { + $attributes = new Attributes(); + $attributes->setAttribute('name', 'test'); + $attributes->setAttribute('version', '1.0.0'); + + $resource = ResourceInfo::create($attributes); + + $attributes->setAttribute('version', '2.0.0'); + + $this->assertEquals('1.0.0', $resource->getAttributes()->getAttribute('version')->getValue()); + } +} diff --git a/tests/unit/Trace/TracingTest.php b/tests/unit/Trace/TracingTest.php index de0c8bc15..c65b5a022 100644 --- a/tests/unit/Trace/TracingTest.php +++ b/tests/unit/Trace/TracingTest.php @@ -34,11 +34,12 @@ public function testTracerSpanContextRestore() { // todo: stop making a new span when a trace is made and then use getTracer instead of new Tracer. $tracerProvider = new SDK\TracerProvider(); - $tracer = new Tracer($tracerProvider); + $instrumentationLibrary = new SDK\InstrumentationLibrary('OpenTelemetry.TracingTest.Test'); + $tracer = new Tracer($tracerProvider, $instrumentationLibrary); $spanContext = $tracer->getActiveSpan()->getContext(); $spanContext2 = SpanContext::restore($spanContext->getTraceId(), $spanContext->getSpanId()); - $tracer2 = new Tracer($tracerProvider, $spanContext2); + $tracer2 = new Tracer($tracerProvider, $instrumentationLibrary, $spanContext2); $this->assertSame($tracer->getActiveSpan()->getContext()->getTraceId(), $tracer2->getActiveSpan()->getContext()->getTraceId()); } @@ -56,7 +57,7 @@ public function testSpanNameUpdate() public function testNestedSpans() { $tracerProvider = new SDK\TracerProvider(); - $tracer = new Tracer($tracerProvider); + $tracer = $tracerProvider->getTracer('OpenTelemetry.TracingTest'); $guard = $tracer->startAndActivateSpan('guard.validate'); $connection = $tracer->startAndActivateSpan('guard.validate.connection');