Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
5 changes: 1 addition & 4 deletions opentelemetry-api/src/opentelemetry/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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:
Expand Down
24 changes: 24 additions & 0 deletions opentelemetry-api/src/opentelemetry/trace/span.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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

Expand Down
24 changes: 24 additions & 0 deletions opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
31 changes: 31 additions & 0 deletions opentelemetry-sdk/tests/trace/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down