Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add indicator if SpanContext was propagated from remote parent #516

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 7 additions & 1 deletion opentelemetry-api/src/opentelemetry/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ class SpanContext:
span_id: This span's ID.
trace_flags: Trace options to propagate.
trace_state: Tracing-system-specific info to propagate.
is_remote: True if propagated from a remote parent.
"""

def __init__(
Expand All @@ -327,6 +328,7 @@ def __init__(
span_id: int,
trace_flags: "TraceFlags" = DEFAULT_TRACE_OPTIONS,
trace_state: "TraceState" = DEFAULT_TRACE_STATE,
is_remote: bool = False,
Copy link
Member

Choose a reason for hiding this comment

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

I suggest not having a default here, for the reasons outlined in https://github.com/open-telemetry/opentelemetry-python/pull/516/files/9b92b7aa1e08be55d309ff2b0de266df0ee5fb91#r396611236

Suggested change
span_id: int,
trace_flags: "TraceFlags" = DEFAULT_TRACE_OPTIONS,
trace_state: "TraceState" = DEFAULT_TRACE_STATE,
is_remote: bool = False,
span_id: int,
is_remote: bool,
trace_flags: "TraceFlags" = DEFAULT_TRACE_OPTIONS,
trace_state: "TraceState" = DEFAULT_TRACE_STATE,

) -> None:
if trace_flags is None:
trace_flags = DEFAULT_TRACE_OPTIONS
Expand All @@ -336,13 +338,17 @@ def __init__(
self.span_id = span_id
self.trace_flags = trace_flags
self.trace_state = trace_state
self.is_remote = is_remote

def __repr__(self) -> str:
return "{}(trace_id={}, span_id={}, trace_state={!r})".format(
return (
"{}(trace_id={}, span_id={}, trace_state={!r}, is_remote={})"
).format(
type(self).__name__,
format_trace_id(self.trace_id),
format_span_id(self.span_id),
self.trace_state,
self.is_remote,
)

def is_valid(self) -> bool:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def extract(
span_id=int(span_id, 16),
trace_flags=trace.TraceFlags(trace_flags),
trace_state=tracestate,
is_remote=True,
)
return set_span_in_context(trace.DefaultSpan(span_context), context)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def test_headers_with_tracestate(self):
self.assertEqual(
span_context.trace_state, {"foo": "1", "bar": "2", "baz": "3"}
)
self.assertTrue(span_context.is_remote)
output = {} # type:typing.Dict[str, str]
span = trace.DefaultSpan(span_context)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ def extract(
span_id=int(span_id, 16),
trace_flags=trace.TraceFlags(options),
trace_state=trace.TraceState(),
is_remote=True,
)
)
)
Expand Down
3 changes: 3 additions & 0 deletions opentelemetry-sdk/tests/context/propagation/test_b3_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def test_extract_multi_header(self):
new_carrier[FORMAT.PARENT_SPAN_ID_KEY],
b3_format.format_span_id(parent.context.span_id),
)
self.assertTrue(parent.context.is_remote)
self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

def test_extract_single_header(self):
Expand All @@ -111,6 +112,7 @@ def test_extract_single_header(self):
b3_format.format_span_id(child.context.span_id),
)
self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")
self.assertTrue(parent.context.is_remote)

child, parent, new_carrier = get_child_parent_new_carrier(
{
Expand All @@ -134,6 +136,7 @@ def test_extract_single_header(self):
new_carrier[FORMAT.PARENT_SPAN_ID_KEY],
b3_format.format_span_id(parent.context.span_id),
)
self.assertTrue(parent.context.is_remote)
self.assertEqual(new_carrier[FORMAT.SAMPLED_KEY], "1")

def test_extract_header_precedence(self):
Expand Down
6 changes: 6 additions & 0 deletions opentelemetry-sdk/tests/trace/test_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,12 @@ def test_default_span_resource(self):
# pylint: disable=protected-access
self.assertIs(span.resource, resources._EMPTY_RESOURCE)

def test_span_context_remote_flag(self):
Copy link
Member

Choose a reason for hiding this comment

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

maybe add a docstring saying that the expected behavior is is_remote is False by default? I guess it's implicit, I just like having some human description so I can verify the intended behavior.

Copy link
Member

Choose a reason for hiding this comment

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

Actually, I think there should not be a default for is_remote. And if any, the default should be true. This flag might have security implications, e.g. samplers may trust a sampled=True flag on a is_remote=False SpanContext. If the is_remote flag is wrongly False, this might lead to a Denial of Service vulnerability.

Copy link
Member

Choose a reason for hiding this comment

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

I can definitely see that argument, although I'm wondering if denial of service is a concern, there isn't a more robust way of dealing with that. This also assumes that the adapters (include internal/custom ones) also properly use the is_remote flag, which is impossible to enforce.

Copy link
Member

Choose a reason for hiding this comment

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

on the other hand, I feel like is_remote=True as the default is unusual behavior, as the remote span is much more of a special case, and currently users are creating/starting spans with no arguments, which would then make then remote spans by default unless one is aware of that flag, and sets it everywhere.

We may need more opinions here, but I think the default being false ensures minimal friction for usage of the opentelemetry APIs.

Copy link
Member

@Oberon00 Oberon00 Mar 25, 2020

Choose a reason for hiding this comment

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

Sorry, this was a misunderstanding on my side! 🙈
I thought we were talking about manually creating a SpanContext. For starting a new span, having is_remote=False on its SpanContext is the only sane thing to do (and this should not be configurable).

tracer = new_tracer()

span = tracer.start_span("foo")
self.assertFalse(span.context.is_remote)


class TestSpan(unittest.TestCase):
def setUp(self):
Expand Down