diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py index 45b6e768a5f..aed421307ee 100644 --- a/opentelemetry-api/src/opentelemetry/trace/__init__.py +++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py @@ -65,6 +65,7 @@ import typing from opentelemetry import loader +from opentelemetry import types # TODO: quarantine ParentSpan = typing.Optional[typing.Union['Span', 'SpanContext']] @@ -102,6 +103,35 @@ def get_context(self) -> 'SpanContext': A :class:`.SpanContext` with a copy of this span's immutable state. """ + def set_attribute(self: 'Span', + key: str, + value: types.AttributeValue, + ) -> None: + """Sets an Attribute. + + Sets a single Attribute with the key and value passed as arguments. + """ + + def add_event(self: 'Span', + name: str, + attributes: types.Attributes = None, + ) -> None: + """Adds an Event. + + Adds a single Event with the name and, optionally, attributes passed + as arguments. + """ + + def add_link(self: 'Span', + link_target_context: 'SpanContext', + attributes: types.Attributes = None, + ) -> None: + """Adds a Link to another span. + + Adds a single Link from this Span to another Span identified by the + `SpanContext` passed as argument. + """ + class TraceOptions(int): """A bitmask that represents options specific to the trace. diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index f6f129827ed..0a85c14c67b 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -230,7 +230,7 @@ def get_context(self): def set_attribute(self: 'Span', key: str, - value: 'types.AttributeValue' + value: types.AttributeValue, ) -> None: if self.attributes is Span.empty_attributes: self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES) @@ -238,19 +238,23 @@ def set_attribute(self: 'Span', def add_event(self: 'Span', name: str, - attributes: 'types.Attributes', + attributes: types.Attributes = None, ) -> None: if self.events is Span.empty_events: self.events = BoundedList(MAX_NUM_EVENTS) + if attributes is None: + attributes = Span.empty_attributes self.events.append(Event(name, attributes)) def add_link(self: 'Span', - context: 'trace_api.SpanContext', - attributes: 'types.Attributes', + link_target_context: 'trace_api.SpanContext', + attributes: types.Attributes = None, ) -> None: if self.links is Span.empty_links: self.links = BoundedList(MAX_NUM_LINKS) - self.links.append(Link(context, attributes)) + if attributes is None: + attributes = Span.empty_attributes + self.links.append(Link(link_target_context, attributes)) def start(self): if self.start_time is None: diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 7a91d23765e..7a4faf78d11 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -114,6 +114,74 @@ def test_start_span_explicit(self): self.assertIs(tracer.get_current_span(), root) self.assertIsNotNone(child.end_time) + def test_span_members(self): + context = contextvars.ContextVar('test_span_members') + tracer = trace.Tracer(context) + + other_context1 = trace_api.SpanContext( + trace_id=trace.generate_trace_id(), + span_id=trace.generate_span_id() + ) + other_context2 = trace_api.SpanContext( + trace_id=trace.generate_trace_id(), + span_id=trace.generate_span_id() + ) + + self.assertIsNone(tracer.get_current_span()) + + with tracer.start_span('root') as root: + root.set_attribute('component', 'http') + root.set_attribute('http.method', 'GET') + root.set_attribute('http.url', + 'https://example.com:779/path/12/?q=d#123') + root.set_attribute('http.status_code', 200) + root.set_attribute('http.status_text', 'OK') + root.set_attribute('misc.pi', 3.14) + + # Setting an attribute with the same key as an existing attribute + # SHOULD overwrite the existing attribute's value. + root.set_attribute('attr-key', 'attr-value1') + root.set_attribute('attr-key', 'attr-value2') + + root.add_event('event0') + root.add_event('event1', {'name': 'birthday'}) + + root.add_link(other_context1) + root.add_link(other_context2, {'name': 'neighbor'}) + + # The public API does not expose getters. + # Checks by accessing the span members directly + + self.assertEqual(len(root.attributes), 7) + self.assertEqual(root.attributes['component'], 'http') + self.assertEqual(root.attributes['http.method'], 'GET') + self.assertEqual(root.attributes['http.url'], + 'https://example.com:779/path/12/?q=d#123') + self.assertEqual(root.attributes['http.status_code'], 200) + self.assertEqual(root.attributes['http.status_text'], 'OK') + self.assertEqual(root.attributes['misc.pi'], 3.14) + self.assertEqual(root.attributes['attr-key'], 'attr-value2') + + self.assertEqual(len(root.events), 2) + self.assertEqual(root.events[0], + trace.Event(name='event0', + attributes={})) + self.assertEqual(root.events[1], + trace.Event(name='event1', + attributes={'name': 'birthday'})) + + self.assertEqual(len(root.links), 2) + self.assertEqual(root.links[0].context.trace_id, + other_context1.trace_id) + self.assertEqual(root.links[0].context.span_id, + other_context1.span_id) + self.assertEqual(root.links[0].attributes, {}) + self.assertEqual(root.links[1].context.trace_id, + other_context2.trace_id) + self.assertEqual(root.links[1].context.span_id, + other_context2.span_id) + self.assertEqual(root.links[1].attributes, {'name': 'neighbor'}) + class TestSpan(unittest.TestCase):