From 169b3369ee32555ba8e67c202e0a4729527b3710 Mon Sep 17 00:00:00 2001 From: soumyadeepm04 Date: Fri, 5 Jul 2024 14:43:34 -0400 Subject: [PATCH 1/4] optional scope attribute for tracer creation --- .../src/opentelemetry/trace/__init__.py | 15 ++++++++- opentelemetry-api/tests/trace/test_globals.py | 6 ++-- opentelemetry-api/tests/trace/test_proxy.py | 2 ++ .../src/opentelemetry/sdk/trace/__init__.py | 2 ++ .../opentelemetry/sdk/util/instrumentation.py | 32 ++++++++++++++++--- opentelemetry-sdk/tests/metrics/test_point.py | 4 +-- opentelemetry-sdk/tests/trace/test_trace.py | 21 ++++++++++++ 7 files changed, 73 insertions(+), 9 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 28300f408c..5de5a240d6 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -189,6 +189,7 @@ def get_tracer( instrumenting_module_name: str, instrumenting_library_version: typing.Optional[str] = None, schema_url: typing.Optional[str] = None, + attributes: typing.Optional[types.Attributes] = None, ) -> "Tracer": """Returns a `Tracer` for use by the given instrumentation library. @@ -216,6 +217,7 @@ def get_tracer( ``importlib.metadata.version(instrumenting_library_name)``. schema_url: Optional. Specifies the Schema URL of the emitted telemetry. + attributes: Optional. Specifies the attributes of the emitted telemetry. """ @@ -230,6 +232,7 @@ def get_tracer( instrumenting_module_name: str, instrumenting_library_version: typing.Optional[str] = None, schema_url: typing.Optional[str] = None, + attributes: typing.Optional[types.Attributes] = None, ) -> "Tracer": # pylint:disable=no-self-use,unused-argument return NoOpTracer() @@ -249,17 +252,20 @@ def get_tracer( instrumenting_module_name: str, instrumenting_library_version: typing.Optional[str] = None, schema_url: typing.Optional[str] = None, + attributes: typing.Optional[types.Attributes] = None, ) -> "Tracer": if _TRACER_PROVIDER: return _TRACER_PROVIDER.get_tracer( instrumenting_module_name, instrumenting_library_version, schema_url, + attributes, ) return ProxyTracer( instrumenting_module_name, instrumenting_library_version, schema_url, + attributes, ) @@ -407,10 +413,12 @@ def __init__( instrumenting_module_name: str, instrumenting_library_version: typing.Optional[str] = None, schema_url: typing.Optional[str] = None, + attributes: typing.Optional[types.Attributes] = None, ): self._instrumenting_module_name = instrumenting_module_name self._instrumenting_library_version = instrumenting_library_version self._schema_url = schema_url + self._attributes = attributes self._real_tracer: Optional[Tracer] = None self._noop_tracer = NoOpTracer() @@ -424,6 +432,7 @@ def _tracer(self) -> Tracer: self._instrumenting_module_name, self._instrumenting_library_version, self._schema_url, + self._attributes, ) return self._real_tracer return self._noop_tracer @@ -492,6 +501,7 @@ def get_tracer( instrumenting_library_version: typing.Optional[str] = None, tracer_provider: Optional[TracerProvider] = None, schema_url: typing.Optional[str] = None, + attributes: typing.Optional[types.Attributes] = None, ) -> "Tracer": """Returns a `Tracer` for use by the given instrumentation library. @@ -503,7 +513,10 @@ def get_tracer( if tracer_provider is None: tracer_provider = get_tracer_provider() return tracer_provider.get_tracer( - instrumenting_module_name, instrumenting_library_version, schema_url + instrumenting_module_name, + instrumenting_library_version, + schema_url, + attributes, ) diff --git a/opentelemetry-api/tests/trace/test_globals.py b/opentelemetry-api/tests/trace/test_globals.py index fdb213bae9..6860f98e9e 100644 --- a/opentelemetry-api/tests/trace/test_globals.py +++ b/opentelemetry-api/tests/trace/test_globals.py @@ -33,10 +33,12 @@ class TestGlobals(TraceGlobalsTest, unittest.TestCase): def test_get_tracer(mock_tracer_provider): # type: ignore """trace.get_tracer should proxy to the global tracer provider.""" trace.get_tracer("foo", "var") - mock_tracer_provider.get_tracer.assert_called_with("foo", "var", None) + mock_tracer_provider.get_tracer.assert_called_with( + "foo", "var", None, None + ) mock_provider = Mock() trace.get_tracer("foo", "var", mock_provider) - mock_provider.get_tracer.assert_called_with("foo", "var", None) + mock_provider.get_tracer.assert_called_with("foo", "var", None, None) class TestGlobalsConcurrency(TraceGlobalsTest, ConcurrencyTestBase): diff --git a/opentelemetry-api/tests/trace/test_proxy.py b/opentelemetry-api/tests/trace/test_proxy.py index 8c20d05491..2317547e8d 100644 --- a/opentelemetry-api/tests/trace/test_proxy.py +++ b/opentelemetry-api/tests/trace/test_proxy.py @@ -23,6 +23,7 @@ NonRecordingSpan, Span, ) +from opentelemetry.util.types import Attributes from opentelemetry.util._decorator import _agnosticcontextmanager @@ -32,6 +33,7 @@ def get_tracer( instrumenting_module_name: str, instrumenting_library_version: typing.Optional[str] = None, schema_url: typing.Optional[str] = None, + attributes: typing.Optional[Attributes] = None, ) -> trace.Tracer: return TestTracer() diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index a7094b547c..58cbf01e08 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -1230,6 +1230,7 @@ def get_tracer( instrumenting_module_name: str, instrumenting_library_version: typing.Optional[str] = None, schema_url: typing.Optional[str] = None, + attributes: typing.Optional[types.Attributes] = None, ) -> "trace_api.Tracer": if self._disabled: logger.warning("SDK is disabled.") @@ -1267,6 +1268,7 @@ def get_tracer( instrumenting_module_name, instrumenting_library_version, schema_url, + attributes, ), ) diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py b/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py index 085d3fd874..a6fd7d7f66 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py @@ -16,6 +16,9 @@ from deprecated import deprecated +from opentelemetry.attributes import BoundedAttributes +from opentelemetry.util.types import Attributes + class InstrumentationInfo: """Immutable information about an instrumentation library module. @@ -82,22 +85,24 @@ class InstrumentationScope: properties. """ - __slots__ = ("_name", "_version", "_schema_url") + __slots__ = ("_name", "_version", "_schema_url", "_attributes") def __init__( self, name: str, version: Optional[str] = None, schema_url: Optional[str] = None, + attributes: Optional[Attributes] = None, ) -> None: self._name = name self._version = version if schema_url is None: schema_url = "" self._schema_url = schema_url + self._attributes = BoundedAttributes(attributes=attributes) def __repr__(self) -> str: - return f"{type(self).__name__}({self._name}, {self._version}, {self._schema_url})" + return f"{type(self).__name__}({self._name}, {self._version}, {self._schema_url}, {self._attributes})" def __hash__(self) -> int: return hash((self._name, self._version, self._schema_url)) @@ -105,19 +110,31 @@ def __hash__(self) -> int: def __eq__(self, value: object) -> bool: if not isinstance(value, InstrumentationScope): return NotImplemented - return (self._name, self._version, self._schema_url) == ( + return ( + self._name, + self._version, + self._schema_url, + self._attributes, + ) == ( value._name, value._version, value._schema_url, + value._attributes, ) def __lt__(self, value: object) -> bool: if not isinstance(value, InstrumentationScope): return NotImplemented - return (self._name, self._version, self._schema_url) < ( + return ( + self._name, + self._version, + self._schema_url, + self._attributes, + ) < ( value._name, value._version, value._schema_url, + value._attributes, ) @property @@ -132,12 +149,19 @@ def version(self) -> Optional[str]: def name(self) -> str: return self._name + @property + def attributes(self) -> Attributes: + return self._attributes + def to_json(self, indent=4) -> str: return dumps( { "name": self._name, "version": self._version, "schema_url": self._schema_url, + "attributes": ( + dict(self._attributes) if bool(self._attributes) else None + ), }, indent=indent, ) diff --git a/opentelemetry-sdk/tests/metrics/test_point.py b/opentelemetry-sdk/tests/metrics/test_point.py index 20dd0e7238..cff07ff6ae 100644 --- a/opentelemetry-sdk/tests/metrics/test_point.py +++ b/opentelemetry-sdk/tests/metrics/test_point.py @@ -178,7 +178,7 @@ def setUpClass(cls): metrics=[cls.metric_0, cls.metric_1, cls.metric_2], schema_url="schema_url_0", ) - cls.scope_metrics_0_str = f'{{"scope": {{"name": "name_0", "version": "version_0", "schema_url": "schema_url_0"}}, "metrics": [{cls.metric_0_str}, {cls.metric_1_str}, {cls.metric_2_str}], "schema_url": "schema_url_0"}}' + cls.scope_metrics_0_str = f'{{"scope": {{"name": "name_0", "version": "version_0", "schema_url": "schema_url_0", "attributes": null}}, "metrics": [{cls.metric_0_str}, {cls.metric_1_str}, {cls.metric_2_str}], "schema_url": "schema_url_0"}}' cls.scope_metrics_1 = ScopeMetrics( scope=InstrumentationScope( @@ -189,7 +189,7 @@ def setUpClass(cls): metrics=[cls.metric_0, cls.metric_1, cls.metric_2], schema_url="schema_url_1", ) - cls.scope_metrics_1_str = f'{{"scope": {{"name": "name_1", "version": "version_1", "schema_url": "schema_url_1"}}, "metrics": [{cls.metric_0_str}, {cls.metric_1_str}, {cls.metric_2_str}], "schema_url": "schema_url_1"}}' + cls.scope_metrics_1_str = f'{{"scope": {{"name": "name_1", "version": "version_1", "schema_url": "schema_url_1", "attributes": null}}, "metrics": [{cls.metric_0_str}, {cls.metric_1_str}, {cls.metric_2_str}], "schema_url": "schema_url_1"}}' cls.resource_metrics_0 = ResourceMetrics( resource=Resource( diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 9f37564309..ce434dd11d 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -165,6 +165,27 @@ def test_tracer_provider_accepts_concurrent_multi_span_processor(self): span_processor, tracer_provider._active_span_processor ) + def test_get_tracer_sdk(self): + tracer_provider = trace.TracerProvider() + tracer = tracer_provider.get_tracer( + "module_name", + "library_version", + "schema_url", + {"key1": "value1", "key2": 6}, + ) + + self.assertEqual(tracer._instrumentation_scope._name, "module_name") + self.assertEqual( + tracer._instrumentation_scope._version, "library_version" + ) + self.assertEqual( + tracer._instrumentation_scope._schema_url, "schema_url" + ) + self.assertEqual( + tracer._instrumentation_scope._attributes, + {"key1": "value1", "key2": 6}, + ) + @mock.patch.dict("os.environ", {OTEL_SDK_DISABLED: "true"}) def test_get_tracer_with_sdk_disabled(self): tracer_provider = trace.TracerProvider() From 65a818b612938e3810120af4bdc8846f706911f1 Mon Sep 17 00:00:00 2001 From: soumyadeepm04 Date: Fri, 5 Jul 2024 15:21:45 -0400 Subject: [PATCH 2/4] fix lint issue --- opentelemetry-api/tests/trace/test_proxy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opentelemetry-api/tests/trace/test_proxy.py b/opentelemetry-api/tests/trace/test_proxy.py index 2317547e8d..caf847777c 100644 --- a/opentelemetry-api/tests/trace/test_proxy.py +++ b/opentelemetry-api/tests/trace/test_proxy.py @@ -23,8 +23,8 @@ NonRecordingSpan, Span, ) -from opentelemetry.util.types import Attributes from opentelemetry.util._decorator import _agnosticcontextmanager +from opentelemetry.util.types import Attributes class TestProvider(trace.NoOpTracerProvider): From f752b10e30cff067b77f363d929ae5fcbc5513d7 Mon Sep 17 00:00:00 2001 From: soumyadeepm04 Date: Fri, 5 Jul 2024 15:51:03 -0400 Subject: [PATCH 3/4] fix lint issues --- opentelemetry-sdk/tests/trace/test_trace.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index ce434dd11d..d039df51ae 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -173,14 +173,17 @@ def test_get_tracer_sdk(self): "schema_url", {"key1": "value1", "key2": 6}, ) - + # pylint: disable=protected-access self.assertEqual(tracer._instrumentation_scope._name, "module_name") + # pylint: disable=protected-access self.assertEqual( tracer._instrumentation_scope._version, "library_version" ) + # pylint: disable=protected-access self.assertEqual( tracer._instrumentation_scope._schema_url, "schema_url" ) + # pylint: disable=protected-access self.assertEqual( tracer._instrumentation_scope._attributes, {"key1": "value1", "key2": 6}, From b4199cbca718fe16111706eb120fa71b647419b3 Mon Sep 17 00:00:00 2001 From: soumyadeepm04 Date: Thu, 11 Jul 2024 13:51:30 -0400 Subject: [PATCH 4/4] updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44938228ca..c68847fb33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- optional scope attribute for tracer creation + ([#4028](https://github.com/open-telemetry/opentelemetry-python/pull/4028)) - OTLP exporter is encoding invalid span/trace IDs in the logs fix ([#4006](https://github.com/open-telemetry/opentelemetry-python/pull/4006)) - Update sdk process resource detector `process.command_args` attribute to also include the executable itself