Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/5259.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-sdk`: add `ServiceInstanceIdResourceDetector` for populating `service.instance.id`
1 change: 1 addition & 0 deletions opentelemetry-sdk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ otel = "opentelemetry.sdk.resources:OTELResourceDetector"
process = "opentelemetry.sdk.resources:ProcessResourceDetector"
os = "opentelemetry.sdk.resources:OsResourceDetector"
host = "opentelemetry.sdk.resources:_HostResourceDetector"
service_instance = "opentelemetry.sdk.resources:ServiceInstanceIdResourceDetector"

[project.urls]
Homepage = "https://github.com/open-telemetry/opentelemetry-python/tree/main/opentelemetry-sdk"
Expand Down
51 changes: 42 additions & 9 deletions opentelemetry-sdk/src/opentelemetry/sdk/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@
import platform
import socket
import sys
import threading
import typing
import uuid
from collections.abc import Sequence
from json import dumps
from os import environ
Expand Down Expand Up @@ -278,6 +280,11 @@ def to_json(self, indent: int | None = 4) -> str:
)


_service_instance_id: str | None = None
_service_instance_id_pid: int | None = None
_service_instance_id_lock = threading.Lock()


Comment thread
herin049 marked this conversation as resolved.
class ResourceDetector(abc.ABC):
def __init__(self, raise_on_error: bool = False) -> None:
self.raise_on_error = raise_on_error
Expand Down Expand Up @@ -462,15 +469,41 @@ def detect(self) -> "Resource":
)


class ServiceInstanceIdResourceDetector(ResourceDetector):
"""Detects service.instance.id as a random UUID v4.

Per the OpenTelemetry specification, SDKs SHOULD generate a random v1/v4
UUID for service.instance.id to uniquely identify each service instance.
The ID is shared across all detector instances within the same process and
regenerated automatically when the process PID changes (e.g. after a fork).
"""

def detect(self) -> "Resource":
# pylint: disable-next=global-statement
global _service_instance_id, _service_instance_id_pid
with _service_instance_id_lock:
current_pid = os.getpid()
if (
_service_instance_id is None
or _service_instance_id_pid != current_pid
):
_service_instance_id = str(uuid.uuid4())
_service_instance_id_pid = current_pid
instance_id = _service_instance_id
return Resource({SERVICE_INSTANCE_ID: instance_id})


def _build_resource_detectors() -> list["ResourceDetector"]:
"""Returns the ordered list of resource detectors to use for Resource.create.

Fast path: if no extra detectors are configured, returns only
OTELResourceDetector without scanning entry_points.
Fast path: if no extra detectors are configured, returns only the two
built-in detectors without scanning entry_points.

"otel" (OTELResourceDetector) defaults to last position so that
OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME take highest merge priority,
but an explicit position in OTEL_EXPERIMENTAL_RESOURCE_DETECTORS is respected.
"service_instance" (ServiceInstanceIdResourceDetector) and "otel"
(OTELResourceDetector) are always appended as defaults. "otel" is last so
Comment thread
herin049 marked this conversation as resolved.
that OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME take highest merge
priority, but an explicit position in OTEL_EXPERIMENTAL_RESOURCE_DETECTORS
is respected for either name.
"""
detector_names: list[str] = list(
dict.fromkeys(
Expand All @@ -481,13 +514,13 @@ def _build_resource_detectors() -> list["ResourceDetector"]:
).split(",")
if name.strip()
]
+ ["otel"]
+ ["service_instance", "otel"]
)
)

# Fast path: only the built-in "otel" detector — no entry_points scan needed.
if detector_names == ["otel"]:
return [OTELResourceDetector()]
# Fast path: only the two built-in detectors — no entry_points scan needed.
if detector_names == ["service_instance", "otel"]:
return [ServiceInstanceIdResourceDetector(), OTELResourceDetector()]

# pylint: disable=import-outside-toplevel
from opentelemetry.util._importlib_metadata import ( # noqa: PLC0415
Expand Down
Loading
Loading