Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.
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
8 changes: 4 additions & 4 deletions opencensus/trace/propagation/b3_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ def from_headers(self, headers):
sampled = headers.get(_SAMPLED_KEY)

if sampled is not None:
if len(sampled) != 1:
return SpanContext(from_header=False)

sampled = sampled in ('1', 'd')
# The specification encodes an enabled tracing decision as "1".
# In the wild pre-standard implementations might still send "true".
# "d" is set in the single header case when debugging is enabled.
sampled = sampled.lower() in ('1', 'd', 'true')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would you put a comment here?
BTW, do we know why we have 'd'?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BTW, do we know why we have 'd'?

It is meant for debugging purpose: https://github.com/openzipkin/b3-propagation#sampling-state-2

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I see. Thank you @MaxWinterstein!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After reading the spec again I think the current implemntation is broken.

When debugging is enabled either via 'd' in sampled or X-B3-Flags: 1 we should enable TraceOptions, or in other word force sampling.
In the current implementation always enables TraceOption when sampled is set.

X-B3-Flags: 1 is also not forwarded.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Maybe the docstring are just a bit confusing for me and in OpenCensus enabled TraceOptions means enabled sampling and is not related to X-B3-Flags.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, I found this:

    # From opencensus.trace.tracer.Tracer#should_sample
    def should_sample(self):
        """Determine whether to sample this request or not.
        If the context enables tracing, return True.
        Else follow the decision of the sampler.

        :rtype: bool
        :returns: Whether to trace the request or not.
        """
        return self.span_context.trace_options.enabled \
            or self.sampler.should_sample(self.span_context.trace_id)

This means with the current b3_format implementation we always overrule the sampler if sampling is enabled via the incoming header.

I am not deep enough in this to judge if this is the intended behavior or not. But at least in my current use case the headers are always set by the edge proxy. So my gut feeling is that the sampler should be able to disable tracing. Which does not work right now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Take a stepping back, I think the right design would be:

  1. By default, we shouldn't sample in.
  2. If there is a sample flag = ENABLED in the incoming request, it should be respected (which means we should sample in).
  3. If there is no sample flag, or the sample flag = DISABLED, we should not sample unless there is a sampler configured (e.g. percentage sampler).
  4. The configured sampler can be overridden by the agent/collector policy. (This has many advantages, such like throttling, defer decision to the agent, etc.).
  5. The agent/collector policy cannot override scenario 2.
def make_sample_decision():
    if (sample_flag_from_request == ENABLED):
        return True
    if (has_agent_sampling_policy()):
        return agent_sample_decision()
    if (has_sampler()):
        return sampler_decision()
    return False

@c24t @aberres please share your thoughts.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sounds sensible. There is just one thing I do not understand:

What is the difference between an incoming trace with sampling enabled and debug enabled?
I was under the impression that 'debug' forces sampling no matter what. But in our case when sampling is enabled we always sample as well.

Another question is if the debug flag must be forwarded.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Here goes my personally opinion:

  1. 'Debug' forces sampling no matter what.
  2. 'Debug' flag must be forwarded. Services in another trust boundary can decide to remove the flag for security considerations.

else:
# If there's no incoming sampling decision, it was deferred to us.
# Even though we set it to False here, we might still sample
Expand Down
49 changes: 35 additions & 14 deletions tests/unit/trace/propagation/test_b3_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,44 @@ def test_from_headers_no_headers(self):
def test_from_headers_keys_exist(self):
test_trace_id = '6e0c63257de34c92bf9efcd03927272e'
test_span_id = '00f067aa0ba902b7'
test_sampled = '1'

headers = {
b3_format._TRACE_ID_KEY: test_trace_id,
b3_format._SPAN_ID_KEY: test_span_id,
b3_format._SAMPLED_KEY: test_sampled,
}
for test_sampled in ['1', 'True', 'true', 'd']:
headers = {
b3_format._TRACE_ID_KEY: test_trace_id,
b3_format._SPAN_ID_KEY: test_span_id,
b3_format._SAMPLED_KEY: test_sampled,
}

propagator = b3_format.B3FormatPropagator()
span_context = propagator.from_headers(headers)
propagator = b3_format.B3FormatPropagator()
span_context = propagator.from_headers(headers)

self.assertEqual(span_context.trace_id, test_trace_id)
self.assertEqual(span_context.span_id, test_span_id)
self.assertEqual(
span_context.trace_options.enabled,
bool(test_sampled)
)
self.assertEqual(span_context.trace_id, test_trace_id)
self.assertEqual(span_context.span_id, test_span_id)
self.assertEqual(
span_context.trace_options.enabled,
True
)

def test_from_headers_keys_exist_disabled_sampling(self):
test_trace_id = '6e0c63257de34c92bf9efcd03927272e'
test_span_id = '00f067aa0ba902b7'

for test_sampled in ['0', 'False', 'false', None]:
headers = {
b3_format._TRACE_ID_KEY: test_trace_id,
b3_format._SPAN_ID_KEY: test_span_id,
b3_format._SAMPLED_KEY: test_sampled,
}

propagator = b3_format.B3FormatPropagator()
span_context = propagator.from_headers(headers)

self.assertEqual(span_context.trace_id, test_trace_id)
self.assertEqual(span_context.span_id, test_span_id)
self.assertEqual(
span_context.trace_options.enabled,
False
)

def test_from_headers_keys_not_exist(self):
propagator = b3_format.B3FormatPropagator()
Expand Down