Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
30 changes: 30 additions & 0 deletions opentelemetry-api/src/opentelemetry/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
import typing

from opentelemetry import loader
from opentelemetry import types

# TODO: quarantine
ParentSpan = typing.Optional[typing.Union['Span', 'SpanContext']]
Expand Down Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One open question is whether the caller ever needs access to the events or links they create (in which case we need the classes in the API) or if we should treat them as part of the implementation of these methods.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's best to start out with the API that exposes as few details as possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two places where I've seen Event/Link and related members being publicly used/exposed : 1) SpanData (but as that is probable going to the SDK at the very least) and 2) MessageEvent (which extends Event, at least in Java: https://github.com/open-telemetry/opentelemetry-java/blob/master/contrib/trace_utils/src/main/java/io/opentelemetry/contrib/trace/MessageEvent.java).

Makes me feel we should include these Event, etc classes here.


Adds a single Event with the name and, optionally, attributes passed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Adds a single Event with the name and, optionally, attributes passed
Adds a single `Event` with the name and, optionally, attributes passed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have an explicit Event class? If not, then that suggestion would break the doc build.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, we don't. We have the following

Event = namedtuple('Event', ('name', 'attributes'))

But that's in the SDK, not exposed in the API.

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.
Expand Down
14 changes: 9 additions & 5 deletions opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,27 +230,31 @@ 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)
self.attributes[key] = value

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It somewhat irks me that we can't use Span.empty_attributes as default and leave out this check, because the API already specifies a default argument of None. But I know of no better way to write this than the one you used here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially tried to use {} as default value but the CI complained with:

Dangerous default value {} as argument

and I read recommendations to use None in that case.

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

Expand Down