-
Notifications
You must be signed in to change notification settings - Fork 212
feat: Add Apache and FPM resource detectors with stable service instance IDs #1628
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
cedricziel
wants to merge
9
commits into
open-telemetry:main
Choose a base branch
from
cedricziel:resource-detector-fix
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
4e92858
Add Apache and FPM resource detectors with stable service instance IDs
cedricziel db3b41a
style
cedricziel 164d09c
Improve OpenTelemetry semantic conventions compliance and fix PHPStan…
cedricziel 0512473
Fix service.instance.id to comply with OpenTelemetry spec using UUID v5
cedricziel d2b116f
Add Kubernetes resource detector with stable service instance IDs
cedricziel 52b1fda
chore: more kubernetes attributes
cedricziel 4f5b963
chore: use constants
cedricziel a1de65b
chore: register component
cedricziel ce9ec3a
Update src/SDK/Resource/Detectors/Apache.php
brettmc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OpenTelemetry\Config\SDK\ComponentProvider\Detector; | ||
|
||
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider; | ||
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry; | ||
use OpenTelemetry\Config\SDK\Configuration\Context; | ||
use OpenTelemetry\SDK\Resource\Detectors\Apache as ApacheDetector; | ||
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface; | ||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; | ||
use Symfony\Component\Config\Definition\Builder\NodeBuilder; | ||
|
||
/** | ||
* @implements ComponentProvider<ResourceDetectorInterface> | ||
*/ | ||
final class Apache implements ComponentProvider | ||
{ | ||
/** | ||
* @param array{} $properties | ||
*/ | ||
public function createPlugin(array $properties, Context $context): ResourceDetectorInterface | ||
{ | ||
return new ApacheDetector(); | ||
} | ||
|
||
public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $builder): ArrayNodeDefinition | ||
{ | ||
return $builder->arrayNode('apache'); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OpenTelemetry\Config\SDK\ComponentProvider\Detector; | ||
|
||
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider; | ||
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry; | ||
use OpenTelemetry\Config\SDK\Configuration\Context; | ||
use OpenTelemetry\SDK\Resource\Detectors\Fpm as FpmDetector; | ||
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface; | ||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; | ||
use Symfony\Component\Config\Definition\Builder\NodeBuilder; | ||
|
||
/** | ||
* @implements ComponentProvider<ResourceDetectorInterface> | ||
*/ | ||
final class Fpm implements ComponentProvider | ||
{ | ||
/** | ||
* @param array{} $properties | ||
*/ | ||
public function createPlugin(array $properties, Context $context): ResourceDetectorInterface | ||
{ | ||
return new FpmDetector(); | ||
} | ||
|
||
public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $builder): ArrayNodeDefinition | ||
{ | ||
return $builder->arrayNode('fpm'); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OpenTelemetry\Config\SDK\ComponentProvider\Detector; | ||
|
||
use OpenTelemetry\Config\SDK\Configuration\ComponentProvider; | ||
use OpenTelemetry\Config\SDK\Configuration\ComponentProviderRegistry; | ||
use OpenTelemetry\Config\SDK\Configuration\Context; | ||
use OpenTelemetry\SDK\Resource\Detectors\Kubernetes as KubernetesDetector; | ||
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface; | ||
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; | ||
use Symfony\Component\Config\Definition\Builder\NodeBuilder; | ||
|
||
/** | ||
* @implements ComponentProvider<ResourceDetectorInterface> | ||
*/ | ||
final class Kubernetes implements ComponentProvider | ||
{ | ||
/** | ||
* @param array{} $properties | ||
*/ | ||
public function createPlugin(array $properties, Context $context): ResourceDetectorInterface | ||
{ | ||
return new KubernetesDetector(); | ||
} | ||
|
||
public function getConfig(ComponentProviderRegistry $registry, NodeBuilder $builder): ArrayNodeDefinition | ||
{ | ||
return $builder->arrayNode('kubernetes'); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OpenTelemetry\SDK\Resource\Detectors; | ||
|
||
use function apache_get_version; | ||
use function function_exists; | ||
use function gethostname; | ||
use OpenTelemetry\SDK\Common\Attribute\Attributes; | ||
use OpenTelemetry\SDK\Common\Configuration\Configuration; | ||
use OpenTelemetry\SDK\Common\Configuration\Variables; | ||
|
||
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface; | ||
use OpenTelemetry\SDK\Resource\ResourceInfo; | ||
use OpenTelemetry\SDK\Resource\ResourceInfoFactory; | ||
use OpenTelemetry\SemConv\ResourceAttributes; | ||
use function php_sapi_name; | ||
use Ramsey\Uuid\Uuid; | ||
|
||
/** | ||
* Apache resource detector that provides stable service instance IDs to avoid high cardinality issues. | ||
* | ||
* For Apache mod_php environments, generates a stable instance ID based on the server name and hostname | ||
* rather than using random UUIDs which cause cardinality explosion in metrics. | ||
*/ | ||
final class Apache implements ResourceDetectorInterface | ||
{ | ||
public function getResource(): ResourceInfo | ||
{ | ||
// Only activate for Apache SAPI | ||
if (!$this->isApacheSapi()) { | ||
return ResourceInfoFactory::emptyResource(); | ||
} | ||
|
||
$attributes = [ | ||
ResourceAttributes::SERVICE_INSTANCE_ID => $this->getStableInstanceId(), | ||
]; | ||
|
||
// Add service name if configured | ||
$serviceName = Configuration::has(Variables::OTEL_SERVICE_NAME) | ||
? Configuration::getString(Variables::OTEL_SERVICE_NAME) | ||
: null; | ||
|
||
if ($serviceName !== null) { | ||
$attributes[ResourceAttributes::SERVICE_NAME] = $serviceName; | ||
} | ||
|
||
// Add Apache-specific attributes | ||
if (function_exists('apache_get_version')) { | ||
$attributes[ResourceAttributes::WEBENGINE_NAME] = 'apache'; | ||
$apacheFullVersion = apache_get_version(); | ||
|
||
// Extract just the version number for webengine.version (e.g. "2.4.41" from "Apache/2.4.41 (Ubuntu)") | ||
$versionNumber = $this->extractApacheVersionNumber($apacheFullVersion); | ||
if ($versionNumber !== null) { | ||
$attributes[ResourceAttributes::WEBENGINE_VERSION] = $versionNumber; | ||
} | ||
|
||
// webengine.description should contain detailed version and edition information | ||
$attributes[ResourceAttributes::WEBENGINE_DESCRIPTION] = $apacheFullVersion; | ||
} | ||
|
||
$serverName = $this->getServerName(); | ||
if ($serverName !== null) { | ||
// Use a custom attribute for server name since it's not part of webengine semantics | ||
$attributes['webserver.server_name'] = $serverName; | ||
} | ||
|
||
return ResourceInfo::create(Attributes::create($attributes), ResourceAttributes::SCHEMA_URL); | ||
} | ||
|
||
/** | ||
* Generate a stable service instance ID for Apache processes. | ||
* | ||
* Uses server name + hostname + document root to create a deterministic UUID v5 that remains | ||
* consistent across Apache process restarts within the same virtual host. | ||
*/ | ||
private function getStableInstanceId(): string | ||
{ | ||
$components = [ | ||
'apache', | ||
$this->getServerName() ?? 'default', | ||
gethostname() ?: 'localhost', | ||
$this->getDocumentRoot() ?? '/var/www', | ||
]; | ||
|
||
// Create a stable UUID v5 using a namespace UUID and deterministic name | ||
$namespace = Uuid::fromString('4d63009a-8d0f-11ee-aad7-4c796ed8e320'); | ||
$name = implode('-', $components); | ||
|
||
return Uuid::uuid5($namespace, $name)->toString(); | ||
cedricziel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
/** | ||
* Check if running under Apache SAPI. | ||
*/ | ||
private function isApacheSapi(): bool | ||
{ | ||
$sapi = php_sapi_name(); | ||
|
||
return $sapi === 'apache2handler' || | ||
$sapi === 'apache' || | ||
str_starts_with($sapi, 'apache'); | ||
} | ||
|
||
/** | ||
* Get the Apache server name from configuration. | ||
*/ | ||
private function getServerName(): ?string | ||
{ | ||
return $_SERVER['SERVER_NAME'] ?? $_SERVER['HTTP_HOST'] ?? null; | ||
} | ||
|
||
/** | ||
* Get the document root for this Apache instance. | ||
*/ | ||
private function getDocumentRoot(): ?string | ||
{ | ||
return $_SERVER['DOCUMENT_ROOT'] ?? null; | ||
} | ||
|
||
/** | ||
* Extract version number from Apache version string. | ||
* | ||
* Examples: | ||
* "Apache/2.4.41 (Ubuntu)" -> "2.4.41" | ||
* "Apache/2.2.34 (Amazon)" -> "2.2.34" | ||
*/ | ||
private function extractApacheVersionNumber(string $apacheVersion): ?string | ||
{ | ||
// Match pattern like "Apache/2.4.41" and extract the version number | ||
if (preg_match('/Apache\/(\d+\.\d+(?:\.\d+)?)/', $apacheVersion, $matches)) { | ||
return $matches[1]; | ||
} | ||
|
||
return null; | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace OpenTelemetry\SDK\Resource\Detectors; | ||
|
||
use function function_exists; | ||
use function gethostname; | ||
use OpenTelemetry\SDK\Common\Attribute\Attributes; | ||
use OpenTelemetry\SDK\Common\Configuration\Configuration; | ||
use OpenTelemetry\SDK\Common\Configuration\Variables; | ||
use OpenTelemetry\SDK\Resource\ResourceDetectorInterface; | ||
|
||
use OpenTelemetry\SDK\Resource\ResourceInfo; | ||
use OpenTelemetry\SDK\Resource\ResourceInfoFactory; | ||
use OpenTelemetry\SemConv\ResourceAttributes; | ||
use function php_sapi_name; | ||
use Ramsey\Uuid\Uuid; | ||
|
||
/** | ||
* FPM resource detector that provides stable service instance IDs to avoid high cardinality issues. | ||
* | ||
* For FPM environments, generates a stable instance ID based on the pool name and hostname | ||
* rather than using random UUIDs which cause cardinality explosion in metrics. | ||
*/ | ||
final class Fpm implements ResourceDetectorInterface | ||
{ | ||
public function getResource(): ResourceInfo | ||
{ | ||
// Only activate for FPM SAPI | ||
if (php_sapi_name() !== 'fpm-fcgi') { | ||
return ResourceInfoFactory::emptyResource(); | ||
} | ||
|
||
$attributes = [ | ||
ResourceAttributes::SERVICE_INSTANCE_ID => $this->getStableInstanceId(), | ||
]; | ||
|
||
// Add service name if configured | ||
$serviceName = Configuration::has(Variables::OTEL_SERVICE_NAME) | ||
? Configuration::getString(Variables::OTEL_SERVICE_NAME) | ||
: null; | ||
|
||
if ($serviceName !== null) { | ||
$attributes[ResourceAttributes::SERVICE_NAME] = $serviceName; | ||
} | ||
|
||
// Add FPM-specific attributes | ||
if (function_exists('fastcgi_finish_request')) { | ||
$poolName = $this->getFpmPoolName(); | ||
if ($poolName !== null) { | ||
$attributes['process.runtime.pool'] = $poolName; | ||
} | ||
} | ||
|
||
return ResourceInfo::create(Attributes::create($attributes), ResourceAttributes::SCHEMA_URL); | ||
} | ||
|
||
/** | ||
* Generate a stable service instance ID for FPM processes. | ||
* | ||
* Uses pool name + hostname to create a deterministic UUID v5 that remains | ||
* consistent across FPM process restarts within the same pool. | ||
*/ | ||
private function getStableInstanceId(): string | ||
{ | ||
$components = [ | ||
'fpm', | ||
$this->getFpmPoolName() ?? 'default', | ||
gethostname() ?: 'localhost', | ||
]; | ||
|
||
// Create a stable UUID v5 using a namespace UUID and deterministic name | ||
$namespace = Uuid::fromString('6ba7b810-9dad-11d1-80b4-00c04fd430c8'); // DNS namespace UUID | ||
$name = implode('-', $components); | ||
|
||
return Uuid::uuid5($namespace, $name)->toString(); | ||
} | ||
|
||
/** | ||
* Attempt to determine the FPM pool name from environment or server variables. | ||
*/ | ||
private function getFpmPoolName(): ?string | ||
{ | ||
// Try common FPM pool identification methods | ||
if (isset($_SERVER['FPM_POOL'])) { | ||
return $_SERVER['FPM_POOL']; | ||
} | ||
|
||
if (isset($_ENV['FPM_POOL'])) { | ||
return $_ENV['FPM_POOL']; | ||
} | ||
|
||
// Fallback: try to extract from process title if available | ||
if (function_exists('cli_get_process_title')) { | ||
$title = cli_get_process_title(); | ||
if ($title && preg_match('/pool\s+(\w+)/', $title, $matches)) { | ||
return $matches[1]; | ||
} | ||
} | ||
|
||
return null; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this should not be part of the detectors. Users can use these detectors together with the
service
detector if they want to readOTEL_SERVICE_NAME
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. This duplicates the
service
detector, which is mandatory since a recent change (so it should always run). In practice, that might also clobber theservice.instance.id
that this detector sets? I think we need an integration test forServiceInfoFactory
, because users generally rely on this to set up resources, and it applies theservice
detector last.