From c73ca5891fcd300cdfa86934ca0dc3755224af3e Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Fri, 5 Jan 2024 23:23:52 +0000 Subject: [PATCH 1/9] Add Span.add_link() method to add link after span start. --- .../src/opentelemetry/trace/__init__.py | 5 +-- .../src/opentelemetry/trace/span.py | 24 ++++++++++++++ .../src/opentelemetry/sdk/trace/__init__.py | 24 ++++++++++++++ opentelemetry-sdk/tests/trace/test_trace.py | 31 +++++++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index bf9e0b89a45..9f9abe0f719 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -85,7 +85,6 @@ from deprecated import deprecated from opentelemetry import context as context_api -from opentelemetry.attributes import BoundedAttributes # type: ignore from opentelemetry.context.context import Context from opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER from opentelemetry.trace.propagation import ( @@ -144,9 +143,7 @@ def __init__( attributes: types.Attributes = None, ) -> None: super().__init__(context) - self._attributes = BoundedAttributes( - attributes=attributes - ) # type: types.Attributes + self._attributes = attributes @property def attributes(self) -> types.Attributes: diff --git a/opentelemetry-api/src/opentelemetry/trace/span.py b/opentelemetry-api/src/opentelemetry/trace/span.py index 805b2b06b18..4c0ba37dd93 100644 --- a/opentelemetry-api/src/opentelemetry/trace/span.py +++ b/opentelemetry-api/src/opentelemetry/trace/span.py @@ -118,6 +118,23 @@ def add_event( timestamp if the `timestamp` argument is omitted. """ + @abc.abstractmethod + def add_link( + self, + context: "SpanContext", + attributes: types.Attributes = None, + ) -> None: + """Adds a `Link`. + + Adds a single `Link` with the `SpanContext` of the span to link to and, + optionally, attributes passed as arguments. Implementations may ignore + calls with an invalid span context. + + Note: It is preferred to add links at span creation, instead of calling + this method later since samplers can only consider information already + present during span creation. + """ + @abc.abstractmethod def update_name(self, name: str) -> None: """Updates the `Span` name. @@ -525,6 +542,13 @@ def add_event( ) -> None: pass + def add_link( + self, + context: "SpanContext", + attributes: types.Attributes = None, + ) -> None: + pass + def update_name(self, name: str) -> None: pass diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 6dae70b2f6b..c707eb52bb1 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -850,6 +850,30 @@ def add_event( ) ) + @_check_span_ended + def _add_link(self, link: trace_api.Link) -> None: + self._links.append(link) + + def add_link( + self, + context: SpanContext, + attributes: types.Attributes = None, + ) -> None: + if context is None or not context.is_valid: + return + + attributes = BoundedAttributes( + self._limits.max_link_attributes, + attributes, + max_value_len=self._limits.max_attribute_length, + ) + self._add_link( + trace_api.Link( + context=context, + attributes=attributes, + ) + ) + def _readable_span(self) -> ReadableSpan: return ReadableSpan( name=self._name, diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 4150d60d104..40f4f4b3162 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -900,6 +900,37 @@ def test_links(self): with self.assertRaises(TypeError): root.links[1].attributes["name"] = "new_neighbour" + def test_add_link(self): + id_generator = RandomIdGenerator() + other_context = trace_api.SpanContext( + trace_id=id_generator.generate_trace_id(), + span_id=id_generator.generate_span_id(), + is_remote=False, + ) + + with self.tracer.start_as_current_span("root") as root: + root.add_link(other_context, {"name": "neighbor"}) + + self.assertEqual(len(root.links), 1) + self.assertEqual( + root.links[0].context.trace_id, other_context.trace_id + ) + self.assertEqual( + root.links[0].context.span_id, other_context.span_id + ) + self.assertEqual(root.links[0].attributes, {"name": "neighbor"}) + + with self.assertRaises(TypeError): + root.links[0].attributes["name"] = "new_neighbour" + + def test_add_link_with_invalid_span_context(self): + other_context = trace_api.INVALID_SPAN_CONTEXT + + with self.tracer.start_as_current_span("root") as root: + root.add_link(other_context) + + self.assertEqual(len(root.links), 0) + def test_update_name(self): with self.tracer.start_as_current_span("root") as root: # name From 92908f1ea1b59afd98e618d906adb16bc63647a3 Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Fri, 5 Jan 2024 23:41:40 +0000 Subject: [PATCH 2/9] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f359c6b633f..18df5ccbce9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3524](https://github.com/open-telemetry/opentelemetry-python/pull/3524)) - Handle `taskName` `logrecord` attribute ([#3557](https://github.com/open-telemetry/opentelemetry-python/pull/3557)) +- Add `Span.add_link()` method to add link after span start + ([#3618](https://github.com/open-telemetry/opentelemetry-python/pull/3618)) ## Version 1.21.0/0.42b0 (2023-11-01) From 40132ab7d87e01c2018f52f60c135f1d9f9ab8ab Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Wed, 24 Jan 2024 14:37:53 +0000 Subject: [PATCH 3/9] Update CHANGELOG.md --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18df5ccbce9..eae10ceeb92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +- Add `Span.add_link()` method to add link after span start + ([#3618](https://github.com/open-telemetry/opentelemetry-python/pull/3618)) + ## Version 1.22.0/0.43b0 (2023-12-15) - Prometheus exporter sanitize info metric ([#3572](https://github.com/open-telemetry/opentelemetry-python/pull/3572)) @@ -18,8 +21,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3524](https://github.com/open-telemetry/opentelemetry-python/pull/3524)) - Handle `taskName` `logrecord` attribute ([#3557](https://github.com/open-telemetry/opentelemetry-python/pull/3557)) -- Add `Span.add_link()` method to add link after span start - ([#3618](https://github.com/open-telemetry/opentelemetry-python/pull/3618)) ## Version 1.21.0/0.42b0 (2023-11-01) From 1921dbe555e3e25d06aa3e79951fcf32bacfe888 Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Mon, 5 Feb 2024 22:30:48 +0000 Subject: [PATCH 4/9] Unmark Span.add_link() as abstract method and add warning. --- opentelemetry-api/src/opentelemetry/trace/span.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/span.py b/opentelemetry-api/src/opentelemetry/trace/span.py index 4c0ba37dd93..d4e921dc21b 100644 --- a/opentelemetry-api/src/opentelemetry/trace/span.py +++ b/opentelemetry-api/src/opentelemetry/trace/span.py @@ -118,8 +118,7 @@ def add_event( timestamp if the `timestamp` argument is omitted. """ - @abc.abstractmethod - def add_link( + def add_link( # pylint: disable=no-self-use self, context: "SpanContext", attributes: types.Attributes = None, @@ -134,6 +133,7 @@ def add_link( this method later since samplers can only consider information already present during span creation. """ + _logger.warning("Span.add_link() is a no-op.") @abc.abstractmethod def update_name(self, name: str) -> None: From 51bf7ab655a944199e7925b7c19376fb1620082a Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Fri, 5 Jan 2024 23:23:52 +0000 Subject: [PATCH 5/9] Add Span.add_link() method to add link after span start. --- .../src/opentelemetry/trace/__init__.py | 5 +-- .../src/opentelemetry/trace/span.py | 24 ++++++++++++++ .../src/opentelemetry/sdk/trace/__init__.py | 24 ++++++++++++++ opentelemetry-sdk/tests/trace/test_trace.py | 31 +++++++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index bf9e0b89a45..9f9abe0f719 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -85,7 +85,6 @@ from deprecated import deprecated from opentelemetry import context as context_api -from opentelemetry.attributes import BoundedAttributes # type: ignore from opentelemetry.context.context import Context from opentelemetry.environment_variables import OTEL_PYTHON_TRACER_PROVIDER from opentelemetry.trace.propagation import ( @@ -144,9 +143,7 @@ def __init__( attributes: types.Attributes = None, ) -> None: super().__init__(context) - self._attributes = BoundedAttributes( - attributes=attributes - ) # type: types.Attributes + self._attributes = attributes @property def attributes(self) -> types.Attributes: diff --git a/opentelemetry-api/src/opentelemetry/trace/span.py b/opentelemetry-api/src/opentelemetry/trace/span.py index 327d85fef48..7c85035f828 100644 --- a/opentelemetry-api/src/opentelemetry/trace/span.py +++ b/opentelemetry-api/src/opentelemetry/trace/span.py @@ -117,6 +117,23 @@ def add_event( timestamp if the `timestamp` argument is omitted. """ + @abc.abstractmethod + def add_link( + self, + context: "SpanContext", + attributes: types.Attributes = None, + ) -> None: + """Adds a `Link`. + + Adds a single `Link` with the `SpanContext` of the span to link to and, + optionally, attributes passed as arguments. Implementations may ignore + calls with an invalid span context. + + Note: It is preferred to add links at span creation, instead of calling + this method later since samplers can only consider information already + present during span creation. + """ + @abc.abstractmethod def update_name(self, name: str) -> None: """Updates the `Span` name. @@ -523,6 +540,13 @@ def add_event( ) -> None: pass + def add_link( + self, + context: "SpanContext", + attributes: types.Attributes = None, + ) -> None: + pass + def update_name(self, name: str) -> None: pass diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index 344838ba186..f937ece8958 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -850,6 +850,30 @@ def add_event( ) ) + @_check_span_ended + def _add_link(self, link: trace_api.Link) -> None: + self._links.append(link) + + def add_link( + self, + context: SpanContext, + attributes: types.Attributes = None, + ) -> None: + if context is None or not context.is_valid: + return + + attributes = BoundedAttributes( + self._limits.max_link_attributes, + attributes, + max_value_len=self._limits.max_attribute_length, + ) + self._add_link( + trace_api.Link( + context=context, + attributes=attributes, + ) + ) + def _readable_span(self) -> ReadableSpan: return ReadableSpan( name=self._name, diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 4150d60d104..40f4f4b3162 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -900,6 +900,37 @@ def test_links(self): with self.assertRaises(TypeError): root.links[1].attributes["name"] = "new_neighbour" + def test_add_link(self): + id_generator = RandomIdGenerator() + other_context = trace_api.SpanContext( + trace_id=id_generator.generate_trace_id(), + span_id=id_generator.generate_span_id(), + is_remote=False, + ) + + with self.tracer.start_as_current_span("root") as root: + root.add_link(other_context, {"name": "neighbor"}) + + self.assertEqual(len(root.links), 1) + self.assertEqual( + root.links[0].context.trace_id, other_context.trace_id + ) + self.assertEqual( + root.links[0].context.span_id, other_context.span_id + ) + self.assertEqual(root.links[0].attributes, {"name": "neighbor"}) + + with self.assertRaises(TypeError): + root.links[0].attributes["name"] = "new_neighbour" + + def test_add_link_with_invalid_span_context(self): + other_context = trace_api.INVALID_SPAN_CONTEXT + + with self.tracer.start_as_current_span("root") as root: + root.add_link(other_context) + + self.assertEqual(len(root.links), 0) + def test_update_name(self): with self.tracer.start_as_current_span("root") as root: # name From 1ccb7a38060186d651c176538ca228f1dc706dbb Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Fri, 5 Jan 2024 23:41:40 +0000 Subject: [PATCH 6/9] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d014b1e578a..e264e185b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3524](https://github.com/open-telemetry/opentelemetry-python/pull/3524)) - Handle `taskName` `logrecord` attribute ([#3557](https://github.com/open-telemetry/opentelemetry-python/pull/3557)) +- Add `Span.add_link()` method to add link after span start + ([#3618](https://github.com/open-telemetry/opentelemetry-python/pull/3618)) ## Version 1.21.0/0.42b0 (2023-11-01) From 7dc78de5721da7172877ab15b5049e370896b6aa Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Wed, 24 Jan 2024 14:37:53 +0000 Subject: [PATCH 7/9] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e264e185b65..10502f999a5 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 +- Add `Span.add_link()` method to add link after span start + ([#3618](https://github.com/open-telemetry/opentelemetry-python/pull/3618)) - Fix `OTLPMetricExporter` ignores `preferred_aggregation` property ([#3603](https://github.com/open-telemetry/opentelemetry-python/pull/3603)) - Logs: set `observed_timestamp` field @@ -52,8 +54,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ([#3524](https://github.com/open-telemetry/opentelemetry-python/pull/3524)) - Handle `taskName` `logrecord` attribute ([#3557](https://github.com/open-telemetry/opentelemetry-python/pull/3557)) -- Add `Span.add_link()` method to add link after span start - ([#3618](https://github.com/open-telemetry/opentelemetry-python/pull/3618)) ## Version 1.21.0/0.42b0 (2023-11-01) From 4f52379df092b5973e578b508f0f0e480595d451 Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Mon, 5 Feb 2024 22:30:48 +0000 Subject: [PATCH 8/9] Unmark Span.add_link() as abstract method and add warning. --- opentelemetry-api/src/opentelemetry/trace/span.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/span.py b/opentelemetry-api/src/opentelemetry/trace/span.py index 7c85035f828..911e8552a71 100644 --- a/opentelemetry-api/src/opentelemetry/trace/span.py +++ b/opentelemetry-api/src/opentelemetry/trace/span.py @@ -117,8 +117,7 @@ def add_event( timestamp if the `timestamp` argument is omitted. """ - @abc.abstractmethod - def add_link( + def add_link( # pylint: disable=no-self-use self, context: "SpanContext", attributes: types.Attributes = None, @@ -133,6 +132,7 @@ def add_link( this method later since samplers can only consider information already present during span creation. """ + _logger.warning("Span.add_link() is a no-op.") @abc.abstractmethod def update_name(self, name: str) -> None: From ea6ebec28f4d835d18ac2bc8487ad71f448471d5 Mon Sep 17 00:00:00 2001 From: Chittalkumar Patel Date: Mon, 19 Feb 2024 23:15:35 +0000 Subject: [PATCH 9/9] Update warning message for Span.add_link(). --- opentelemetry-api/src/opentelemetry/trace/span.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/opentelemetry-api/src/opentelemetry/trace/span.py b/opentelemetry-api/src/opentelemetry/trace/span.py index 911e8552a71..8201fdb251e 100644 --- a/opentelemetry-api/src/opentelemetry/trace/span.py +++ b/opentelemetry-api/src/opentelemetry/trace/span.py @@ -3,6 +3,7 @@ import re import types as python_types import typing +import warnings from opentelemetry.trace.status import Status, StatusCode from opentelemetry.util import types @@ -132,7 +133,10 @@ def add_link( # pylint: disable=no-self-use this method later since samplers can only consider information already present during span creation. """ - _logger.warning("Span.add_link() is a no-op.") + warnings.warn( + "Span.add_link() not implemented and will be a no-op. " + "Use opentelemetry-sdk >= 1.23 to add links after span creation" + ) @abc.abstractmethod def update_name(self, name: str) -> None: