Skip to content

Commit 03260e6

Browse files
rework ServiceInfoFactory priorities (#1644)
* rework ServiceInfoFactory priorities - default/all: load detectors from registry last - env-provided detectors: load in same order as default/all, then from registry in requested order * split service.instance.id into own detector to avoid always generating service.instance.id (which is useless for fpm/apache), split it into its own detector, ServiceInstance, and don't use it by default. If it is enabled, ensure it runs before registry-provided detectors. * Update src/SDK/Resource/ResourceInfoFactory.php Co-authored-by: Chris Lightfoot-Wild <[email protected]> * override --------- Co-authored-by: Chris Lightfoot-Wild <[email protected]>
1 parent 00417cf commit 03260e6

File tree

9 files changed

+139
-66
lines changed

9 files changed

+139
-66
lines changed

src/SDK/Common/Configuration/KnownValues.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ interface KnownValues
197197
public const VALUE_DETECTORS_SDK = 'sdk';
198198
public const VALUE_DETECTORS_SDK_PROVIDED = 'sdk_provided';
199199
public const VALUE_DETECTORS_SERVICE = 'service';
200+
public const VALUE_DETECTORS_SERVICE_INSTANCE = 'service_instance';
200201
public const VALUE_DETECTORS_COMPOSER = 'composer';
201202
public const OTEL_PHP_DETECTORS = [
202203
self::VALUE_ALL,

src/SDK/Resource/Detectors/Service.php

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface;
1111
use OpenTelemetry\SDK\Resource\ResourceInfo;
1212
use OpenTelemetry\SemConv\ResourceAttributes;
13-
use Ramsey\Uuid\Uuid;
1413

1514
/**
1615
* @see https://github.com/open-telemetry/semantic-conventions/tree/main/docs/resource#service-experimental
@@ -20,18 +19,13 @@ final class Service implements ResourceDetectorInterface
2019
#[\Override]
2120
public function getResource(): ResourceInfo
2221
{
23-
static $serviceInstanceId;
24-
$serviceInstanceId ??= Uuid::uuid4()->toString();
2522
$serviceName = Configuration::has(Variables::OTEL_SERVICE_NAME)
2623
? Configuration::getString(Variables::OTEL_SERVICE_NAME)
2724
: null;
2825

2926
$attributes = [
30-
ResourceAttributes::SERVICE_INSTANCE_ID => $serviceInstanceId,
27+
ResourceAttributes::SERVICE_NAME => $serviceName,
3128
];
32-
if ($serviceName !== null) {
33-
$attributes[ResourceAttributes::SERVICE_NAME] = $serviceName;
34-
}
3529

3630
return ResourceInfo::create(Attributes::create($attributes), ResourceAttributes::SCHEMA_URL);
3731
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenTelemetry\SDK\Resource\Detectors;
6+
7+
use OpenTelemetry\SDK\Common\Attribute\Attributes;
8+
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface;
9+
use OpenTelemetry\SDK\Resource\ResourceInfo;
10+
use OpenTelemetry\SemConv\ResourceAttributes;
11+
use Ramsey\Uuid\Uuid;
12+
13+
/**
14+
* @see https://github.com/open-telemetry/semantic-conventions/tree/main/docs/resource#service-experimental
15+
*
16+
* Spec deviation: Service Instance ID is not generated by the Service detector, as UUID is not useful in shared-nothing
17+
* PHP setups (FPM, Apache), and cannot be replaced by a more useful value due to Service being the last detector.
18+
*/
19+
final class ServiceInstance implements ResourceDetectorInterface
20+
{
21+
#[\Override]
22+
public function getResource(): ResourceInfo
23+
{
24+
static $serviceInstanceId;
25+
$serviceInstanceId ??= Uuid::uuid4()->toString();
26+
27+
$attributes = [
28+
ResourceAttributes::SERVICE_INSTANCE_ID => $serviceInstanceId,
29+
];
30+
31+
return ResourceInfo::create(Attributes::create($attributes), ResourceAttributes::SCHEMA_URL);
32+
}
33+
}

src/SDK/Resource/ResourceInfoFactory.php

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -29,50 +29,51 @@ public static function defaultResource(): ResourceInfo
2929
return (new Detectors\Composite([
3030
new Detectors\Host(),
3131
new Detectors\Process(),
32-
...Registry::resourceDetectors(),
3332
new Detectors\Environment(),
3433
new Detectors\Sdk(),
3534
new Detectors\Service(),
35+
...Registry::resourceDetectors(),
3636
]))->getResource();
3737
}
3838

39+
/**
40+
* Process env-provided detectors:
41+
* - host, process, environment, composer first if requested
42+
* - sdk, service always
43+
* - any other detectors registered in the registry if requested
44+
*/
3945
$resourceDetectors = [];
40-
41-
foreach ($detectors as $detector) {
42-
switch ($detector) {
43-
case Values::VALUE_DETECTORS_ENVIRONMENT:
44-
$resourceDetectors[] = new Detectors\Environment();
45-
46-
break;
47-
case Values::VALUE_DETECTORS_HOST:
48-
$resourceDetectors[] = new Detectors\Host();
49-
50-
break;
51-
case Values::VALUE_DETECTORS_PROCESS:
52-
$resourceDetectors[] = new Detectors\Process();
53-
54-
break;
55-
56-
case Values::VALUE_DETECTORS_COMPOSER:
57-
$resourceDetectors[] = new Detectors\Composer();
58-
59-
break;
60-
case Values::VALUE_DETECTORS_SDK_PROVIDED: //deprecated
61-
case Values::VALUE_DETECTORS_OS: //deprecated
62-
case Values::VALUE_DETECTORS_PROCESS_RUNTIME: //deprecated
63-
case Values::VALUE_NONE:
64-
65-
break;
66-
default:
67-
try {
68-
$resourceDetectors[] = Registry::resourceDetector($detector);
69-
} catch (RuntimeException $e) {
70-
self::logWarning($e->getMessage());
71-
}
46+
foreach ([
47+
Values::VALUE_DETECTORS_HOST => Detectors\Host::class,
48+
Values::VALUE_DETECTORS_PROCESS => Detectors\Process::class,
49+
Values::VALUE_DETECTORS_ENVIRONMENT => Detectors\Environment::class,
50+
Values::VALUE_DETECTORS_COMPOSER => Detectors\Composer::class,
51+
Values::VALUE_DETECTORS_SERVICE_INSTANCE => Detectors\ServiceInstance::class,
52+
] as $detector => $class) {
53+
if (in_array($detector, $detectors)) {
54+
$resourceDetectors[] = new $class();
55+
$detectors = array_diff($detectors, [$detector]);
7256
}
7357
}
7458
$resourceDetectors [] = new Detectors\Sdk();
7559
$resourceDetectors [] = new Detectors\Service();
60+
// Don't try to load mandatory + deprecated detectors
61+
$detectors = array_diff($detectors, [
62+
Values::VALUE_DETECTORS_SDK,
63+
Values::VALUE_DETECTORS_SERVICE,
64+
Values::VALUE_DETECTORS_SDK_PROVIDED, //deprecated
65+
Values::VALUE_DETECTORS_OS, //deprecated
66+
Values::VALUE_DETECTORS_PROCESS_RUNTIME, //deprecated
67+
Values::VALUE_NONE,
68+
]);
69+
70+
foreach ($detectors as $detector) {
71+
try {
72+
$resourceDetectors[] = Registry::resourceDetector($detector);
73+
} catch (RuntimeException $e) {
74+
self::logWarning($e->getMessage());
75+
}
76+
}
7677

7778
return (new Detectors\Composite($resourceDetectors))->getResource();
7879
}

tests/Integration/Config/ConfigurationTest.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,6 @@ public function test_resource_include_exclude(): void
127127
'process.executable.path',
128128
'process.owner',
129129
'process.runtime.name',
130-
'service.instance.id',
131130
'service.name',
132131
'telemetry.distro.name',
133132
'telemetry.distro.version',
@@ -148,7 +147,6 @@ public function test_resource_defaults(): void
148147
{
149148
$expectedKeys = [
150149
'service.name',
151-
'service.instance.id',
152150
'telemetry.distro.name',
153151
'telemetry.distro.version',
154152
'telemetry.sdk.language',

tests/Integration/SDK/Resource/ResourceInfoFactoryTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,16 @@ public function test_composer_default_resources(): void
171171
$this->assertEquals('open-telemetry/opentelemetry', $resource->getAttributes()->get(ResourceAttributes::SERVICE_NAME));
172172
$this->assertEquals(InstalledVersions::getRootPackage()['pretty_version'], $resource->getAttributes()->get(ResourceAttributes::SERVICE_VERSION));
173173
}
174+
175+
public function test_env_doesnt_clobber_service(): void
176+
{
177+
//service.name should be provided by mandatory Service detector, and should not be clobbered by environment detector
178+
$this->setEnvironmentVariable('OTEL_SERVICE_NAME', 'test-service');
179+
$this->setEnvironmentVariable('OTEL_RESOURCE_ATTRIBUTES', 'service.name=ignore-me');
180+
$this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'env');
181+
182+
$resource = ResourceInfoFactory::defaultResource();
183+
184+
$this->assertEquals('test-service', $resource->getAttributes()->get(ResourceAttributes::SERVICE_NAME));
185+
}
174186
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Unit\SDK\Resource\Detectors;
6+
7+
use OpenTelemetry\SDK\Resource\Detectors;
8+
use OpenTelemetry\SemConv\ResourceAttributes;
9+
use OpenTelemetry\Tests\TestState;
10+
use PHPUnit\Framework\Attributes\CoversClass;
11+
use PHPUnit\Framework\TestCase;
12+
13+
#[CoversClass(Detectors\ServiceInstance::class)]
14+
class ServiceInstanceIdTest extends TestCase
15+
{
16+
use TestState;
17+
18+
const UUID_REGEX = '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';
19+
20+
private Detectors\ServiceInstance $detector;
21+
22+
#[\Override]
23+
public function setUp(): void
24+
{
25+
$this->detector = new Detectors\ServiceInstance();
26+
}
27+
28+
public function test_service_get_resource_with_default_service_instance_id(): void
29+
{
30+
$resource = $this->detector->getResource();
31+
32+
$this->assertNotEmpty($resource->getAttributes());
33+
34+
$id = $resource->getAttributes()->get(ResourceAttributes::SERVICE_INSTANCE_ID);
35+
$this->assertMatchesRegularExpression(self::UUID_REGEX, $id);
36+
}
37+
38+
public function test_service_get_resource_multiple_calls_same_service_instance_id(): void
39+
{
40+
$resource1 = $this->detector->getResource();
41+
$resource2 = $this->detector->getResource();
42+
43+
$this->assertSame($resource1->getAttributes()->get(ResourceAttributes::SERVICE_INSTANCE_ID), $resource2->getAttributes()->get(ResourceAttributes::SERVICE_INSTANCE_ID));
44+
}
45+
}

tests/Unit/SDK/Resource/Detectors/ServiceTest.php

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ class ServiceTest extends TestCase
1515
{
1616
use TestState;
1717

18-
const UUID_REGEX = '/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i';
19-
2018
private Detectors\Service $detector;
2119

2220
#[\Override]
@@ -25,24 +23,6 @@ public function setUp(): void
2523
$this->detector = new Detectors\Service();
2624
}
2725

28-
public function test_service_get_resource_with_default_service_instance_id(): void
29-
{
30-
$resource = $this->detector->getResource();
31-
32-
$this->assertNotEmpty($resource->getAttributes());
33-
34-
$id = $resource->getAttributes()->get(ResourceAttributes::SERVICE_INSTANCE_ID);
35-
$this->assertMatchesRegularExpression(self::UUID_REGEX, $id);
36-
}
37-
38-
public function test_service_get_resource_multiple_calls_same_service_instance_id(): void
39-
{
40-
$resource1 = $this->detector->getResource();
41-
$resource2 = $this->detector->getResource();
42-
43-
$this->assertSame($resource1->getAttributes()->get(ResourceAttributes::SERVICE_INSTANCE_ID), $resource2->getAttributes()->get(ResourceAttributes::SERVICE_INSTANCE_ID));
44-
}
45-
4626
public function test_sdk_get_resource_with_service_name(): void
4727
{
4828
$this->setEnvironmentVariable('OTEL_SERVICE_NAME', 'test-service');

tests/Unit/SDK/Resource/ResourceInfoFactoryTest.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ public function test_resource_with_invalid_environment_variable(): void
108108
#[Group('compliance')]
109109
public function test_resource_from_environment_service_name_takes_precedence_over_resource_attribute(): void
110110
{
111-
$this->setEnvironmentVariable('OTEL_RESOURCE_ATTRIBUTES', 'service.name=bar');
112-
$this->setEnvironmentVariable('OTEL_SERVICE_NAME', 'foo');
111+
$this->setEnvironmentVariable('OTEL_RESOURCE_ATTRIBUTES', 'service.name=from-resource-attributes');
112+
$this->setEnvironmentVariable('OTEL_SERVICE_NAME', 'from-service-name');
113113
$resource = ResourceInfoFactory::defaultResource();
114-
$this->assertEquals('foo', $resource->getAttributes()->get('service.name'));
114+
$this->assertEquals('from-service-name', $resource->getAttributes()->get('service.name'));
115115
}
116116

117117
#[Group('compliance')]
@@ -189,11 +189,20 @@ public function test_environment_get_resource_service_name_precedence_over_resou
189189
{
190190
$this->setEnvironmentVariable('OTEL_RESOURCE_ATTRIBUTES', 'service.name=from-env');
191191
$this->setEnvironmentVariable('OTEL_SERVICE_NAME', 'from-service-name');
192-
$this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'environment');
192+
$this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'env');
193193

194194
$resource = ResourceInfoFactory::defaultResource();
195195

196196
$this->assertNotEmpty($resource->getAttributes());
197197
$this->assertSame('from-service-name', $resource->getAttributes()->get(ResourceAttributes::SERVICE_NAME));
198198
}
199+
200+
public function test_service_instance_detector(): void
201+
{
202+
$this->setEnvironmentVariable('OTEL_PHP_DETECTORS', 'service_instance');
203+
204+
$resource = ResourceInfoFactory::defaultResource();
205+
206+
$this->assertNotEmpty($resource->getAttributes()->get(ResourceAttributes::SERVICE_INSTANCE_ID));
207+
}
199208
}

0 commit comments

Comments
 (0)