From 1fea42544adb20b16a073f6c294baebdf25d01ed Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Fri, 5 Oct 2018 15:02:56 -0700 Subject: [PATCH 1/4] improve W3C TraceContext compliance --- .../trace_context_http_header_format.py | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/opencensus/trace/propagation/trace_context_http_header_format.py b/opencensus/trace/propagation/trace_context_http_header_format.py index c3b07f4de..499e6ec35 100644 --- a/opencensus/trace/propagation/trace_context_http_header_format.py +++ b/opencensus/trace/propagation/trace_context_http_header_format.py @@ -23,7 +23,7 @@ _TRACEPARENT_HEADER_NAME = 'traceparent' _TRACESTATE_HEADER_NAME = 'tracestate' _TRACE_CONTEXT_HEADER_FORMAT = \ - '([0-9a-f]{2})(-([0-9a-f]{32}))(-([0-9a-f]{16}))?(-([0-9a-f]{2}))?' + '^[ \t]*([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(-.*)?[ \t]*$' _TRACE_CONTEXT_HEADER_RE = re.compile(_TRACE_CONTEXT_HEADER_FORMAT) @@ -41,7 +41,7 @@ def from_header(self, header): :returns: SpanContext generated from the trace context header. """ if header is None: - return SpanContext() + return try: match = re.search(_TRACE_CONTEXT_HEADER_RE, header) @@ -49,36 +49,33 @@ def from_header(self, header): logging.warning( 'Header should be str, got {}. Cannot parse the header.' .format(header.__class__.__name__)) - raise - + return if match: version = match.group(1) + trace_id = match.group(2) + span_id = match.group(3) + trace_options = match.group(4) - if version == '00': - trace_id = match.group(3) - span_id = match.group(5) - trace_options = match.group(7) - - if trace_options is None: - trace_options = 1 - - # Need to convert span_id from hex string to int - span_context = SpanContext( - trace_id=trace_id, - span_id=span_id, - trace_options=TraceOptions(trace_options), - from_header=True) - return span_context - else: - logging.warning( - 'Header format version {} is not supported, generate a new' - 'context instead.'.format(version)) - else: - logging.warning( - 'Cannot parse the header {}, generate a new context instead.' - .format(header)) + if trace_id == '0' * 32 or span_id == '0' * 16: + return - return SpanContext() + if version == '00': + if match.group(5): + return + elif version == 'ff': + return + + # Need to convert span_id from hex string to int + span_context = SpanContext( + trace_id=trace_id, + span_id=span_id, + trace_options=TraceOptions(trace_options), + from_header=True) + return span_context + + logging.warning( + 'Cannot parse the header {}, generate a new context instead.' + .format(header)) def from_headers(self, headers): """Generate a SpanContext object using the W3C Distributed Tracing headers. @@ -94,9 +91,10 @@ def from_headers(self, headers): header = headers.get(_TRACEPARENT_HEADER_NAME) if header is None: return SpanContext() - header = str(header.encode('utf-8')) span_context = self.from_header(header) + if span_context is None: + return SpanContext() header = headers.get(_TRACESTATE_HEADER_NAME) if header is not None: From 88df1309c5689b397a859a20328c4dbb194f3b00 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Mon, 8 Oct 2018 16:03:26 -0700 Subject: [PATCH 2/4] improve W3C TraceContext compliance --- .../trace_context_http_header_format.py | 118 +++++------- .../propagation/tracestate_string_format.py | 2 + opencensus/trace/tracestate.py | 8 +- .../test_trace_context_http_header_format.py | 174 ++++++++---------- 4 files changed, 126 insertions(+), 176 deletions(-) diff --git a/opencensus/trace/propagation/trace_context_http_header_format.py b/opencensus/trace/propagation/trace_context_http_header_format.py index 499e6ec35..7bb091b96 100644 --- a/opencensus/trace/propagation/trace_context_http_header_format.py +++ b/opencensus/trace/propagation/trace_context_http_header_format.py @@ -30,18 +30,21 @@ class TraceContextPropagator(object): """Propagator for processing the trace context HTTP header format.""" - def from_header(self, header): - """Generate a SpanContext object using the trace context header. + def from_headers(self, headers): + """Generate a SpanContext object using the W3C Distributed Tracing headers. - :type header: str - :param header: Trace context header which was extracted from the HTTP - request headers. + :type headers: dict + :param headers: HTTP request headers. :rtype: :class:`~opencensus.trace.span_context.SpanContext` :returns: SpanContext generated from the trace context header. """ + if headers is None: + return SpanContext() + + header = headers.get(_TRACEPARENT_HEADER_NAME) if header is None: - return + return SpanContext() try: match = re.search(_TRACE_CONTEXT_HEADER_RE, header) @@ -49,69 +52,53 @@ def from_header(self, header): logging.warning( 'Header should be str, got {}. Cannot parse the header.' .format(header.__class__.__name__)) - return - if match: - version = match.group(1) - trace_id = match.group(2) - span_id = match.group(3) - trace_options = match.group(4) - - if trace_id == '0' * 32 or span_id == '0' * 16: - return - - if version == '00': - if match.group(5): - return - elif version == 'ff': - return - - # Need to convert span_id from hex string to int - span_context = SpanContext( - trace_id=trace_id, - span_id=span_id, - trace_options=TraceOptions(trace_options), - from_header=True) - return span_context - - logging.warning( - 'Cannot parse the header {}, generate a new context instead.' - .format(header)) + return SpanContext() - def from_headers(self, headers): - """Generate a SpanContext object using the W3C Distributed Tracing headers. + if not match: + return SpanContext() - :type headers: dict - :param headers: HTTP request headers. + version = match.group(1) + trace_id = match.group(2) + span_id = match.group(3) + trace_options = match.group(4) - :rtype: :class:`~opencensus.trace.span_context.SpanContext` - :returns: SpanContext generated from the trace context header. - """ - if headers is None: - return SpanContext() - header = headers.get(_TRACEPARENT_HEADER_NAME) - if header is None: + if trace_id == '0' * 32 or span_id == '0' * 16: return SpanContext() - span_context = self.from_header(header) - if span_context is None: + if version == '00': + if match.group(5): + return SpanContext() + elif version == 'ff': return SpanContext() - header = headers.get(_TRACESTATE_HEADER_NAME) - if header is not None: - span_context.tracestate = \ - TracestateStringFormatter().from_string(header) + span_context = SpanContext( + trace_id=trace_id, + span_id=span_id, + trace_options=TraceOptions(trace_options), + from_header=True) + header = headers.get(_TRACESTATE_HEADER_NAME) + if header is None: + return span_context + try: + tracestate = TracestateStringFormatter().from_string(header) + if tracestate.is_valid(): + span_context.tracestate = \ + TracestateStringFormatter().from_string(header) + except ValueError: + pass return span_context - def to_header(self, span_context): - """Convert a SpanContext object to header string, using version 0. + def to_headers(self, span_context): + """Convert a SpanContext object to W3C Distributed Tracing headers, + using version 0. :type span_context: :class:`~opencensus.trace.span_context.SpanContext` :param span_context: SpanContext object. - :rtype: str - :returns: A trace context header string in trace context HTTP format. + :rtype: dict + :returns: W3C Distributed Tracing headers. """ trace_id = span_context.trace_id span_id = span_context.span_id @@ -120,25 +107,12 @@ def to_header(self, span_context): # Convert the trace options trace_options = '01' if trace_options else '00' - header = '00-{}-{}-{}'.format( - trace_id, - span_id, - trace_options) - return header - - def to_headers(self, span_context): - """Convert a SpanContext object to W3C Distributed Tracing headers, - using version 0. - - :type span_context: - :class:`~opencensus.trace.span_context.SpanContext` - :param span_context: SpanContext object. - - :rtype: dict - :returns: W3C Distributed Tracing headers. - """ headers = { - _TRACEPARENT_HEADER_NAME: self.to_header(span_context), + _TRACEPARENT_HEADER_NAME: '00-{}-{}-{}'.format( + trace_id, + span_id, + trace_options + ), } tracestate = span_context.tracestate if tracestate: diff --git a/opencensus/trace/propagation/tracestate_string_format.py b/opencensus/trace/propagation/tracestate_string_format.py index 4aa83ae31..da2882880 100644 --- a/opencensus/trace/propagation/tracestate_string_format.py +++ b/opencensus/trace/propagation/tracestate_string_format.py @@ -32,6 +32,8 @@ def from_string(self, string): if not match: raise ValueError('illegal key-value format %r' % (member)) key, eq, value = match.groups() + if key in tracestate: + raise ValueError('conflict key {!r}'.format(key)) tracestate[key] = value return tracestate diff --git a/opencensus/trace/tracestate.py b/opencensus/trace/tracestate.py index bcf9dad21..7b86bbdcc 100644 --- a/opencensus/trace/tracestate.py +++ b/opencensus/trace/tracestate.py @@ -15,8 +15,12 @@ from collections import OrderedDict import re -_KEY_FORMAT = r'[a-z][_0-9a-z\-\*\/]{0,255}' -_VALUE_FORMAT = r'[\x20-\x2b\x2d-\x3c\x3e-\x7e]{1,256}' +_KEY_WITHOUT_VENDOR_FORMAT = r'[a-z][_0-9a-z\-\*\/]{0,255}' +_KEY_WITH_VENDOR_FORMAT = \ + r'[a-z][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}' +_KEY_FORMAT = _KEY_WITHOUT_VENDOR_FORMAT + '|' + _KEY_WITH_VENDOR_FORMAT +_VALUE_FORMAT = \ + r'[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]' _KEY_VALIDATION_RE = re.compile('^' + _KEY_FORMAT + '$') _VALUE_VALIDATION_RE = re.compile('^' + _VALUE_FORMAT + '$') diff --git a/tests/unit/trace/propagation/test_trace_context_http_header_format.py b/tests/unit/trace/propagation/test_trace_context_http_header_format.py index 8e0f512e8..6b0825309 100644 --- a/tests/unit/trace/propagation/test_trace_context_http_header_format.py +++ b/tests/unit/trace/propagation/test_trace_context_http_header_format.py @@ -19,20 +19,12 @@ class TestTraceContextPropagator(unittest.TestCase): - def test_from_header_no_header(self): - from opencensus.trace.span_context import SpanContext - - propagator = trace_context_http_header_format.\ - TraceContextPropagator() - span_context = propagator.from_header(None) - - self.assertTrue(isinstance(span_context, SpanContext)) - def test_from_headers_none(self): from opencensus.trace.span_context import SpanContext propagator = trace_context_http_header_format.\ TraceContextPropagator() + span_context = propagator.from_headers(None) self.assertTrue(isinstance(span_context, SpanContext)) @@ -42,6 +34,7 @@ def test_from_headers_empty(self): propagator = trace_context_http_header_format.\ TraceContextPropagator() + span_context = propagator.from_headers({}) self.assertTrue(isinstance(span_context, SpanContext)) @@ -51,135 +44,112 @@ def test_from_headers_with_tracestate(self): propagator = trace_context_http_header_format.\ TraceContextPropagator() + span_context = propagator.from_headers({ - 'traceparent': '00-a66ee7820d074463aff4c617a63e929f-91e072af6a404137-01', + 'traceparent': + '00-12345678901234567890123456789012-1234567890123456-00', 'tracestate': 'foo=1,bar=2,baz=3', }) self.assertTrue(isinstance(span_context, SpanContext)) self.assertTrue(span_context.tracestate) - def test_header_type_error(self): - header = 1234 - - propagator = trace_context_http_header_format.\ - TraceContextPropagator() - - with self.assertRaises(TypeError): - propagator.from_header(header) - - def test_header_version_not_support(self): + def test_header_all_zero(self): from opencensus.trace.span_context import SpanContext - header = '01-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7-00' propagator = trace_context_http_header_format. \ TraceContextPropagator() - span_context = propagator.from_header(header) - self.assertTrue(isinstance(span_context, SpanContext)) + trace_id = '00000000000000000000000000000000' + span_context = propagator.from_headers({ + 'traceparent': + '00-00000000000000000000000000000000-1234567890123456-00', + }) - def test_header_match(self): - # Trace option is not enabled. - header = '00-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7-00' - expected_trace_id = '6e0c63257de34c92bf9efcd03927272e' - expected_span_id = '00f067aa0ba902b7' + self.assertNotEqual(span_context.trace_id, trace_id) - propagator = trace_context_http_header_format.\ - TraceContextPropagator() - span_context = propagator.from_header(header) + span_id = '0000000000000000' + span_context = propagator.from_headers({ + 'traceparent': + '00-12345678901234567890123456789012-0000000000000000-00', + }) - self.assertEqual(span_context.trace_id, expected_trace_id) - self.assertEqual(span_context.span_id, expected_span_id) - self.assertFalse(span_context.trace_options.enabled) + self.assertNotEqual(span_context.span_id, span_id) - # Trace option is enabled. - header = '00-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7-01' - expected_trace_id = '6e0c63257de34c92bf9efcd03927272e' - expected_span_id = '00f067aa0ba902b7' + def test_header_version_not_supported(self): + from opencensus.trace.span_context import SpanContext - propagator = trace_context_http_header_format.\ + propagator = trace_context_http_header_format. \ TraceContextPropagator() - span_context = propagator.from_header(header) - self.assertEqual(span_context.trace_id, expected_trace_id) - self.assertEqual(span_context.span_id, expected_span_id) - self.assertTrue(span_context.trace_options.enabled) - - def test_header_match_no_option(self): - header = '00-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7' - expected_trace_id = '6e0c63257de34c92bf9efcd03927272e' - expected_span_id = '00f067aa0ba902b7' + trace_id = '12345678901234567890123456789012' + span_context = propagator.from_headers({ + 'traceparent': + 'ff-12345678901234567890123456789012-1234567890123456-00', + }) - propagator = trace_context_http_header_format.\ - TraceContextPropagator() - span_context = propagator.from_header(header) + self.assertNotEqual(span_context.trace_id, trace_id) - self.assertEqual(span_context.trace_id, expected_trace_id) - self.assertEqual(span_context.span_id, expected_span_id) - self.assertTrue(span_context.trace_options.enabled) + span_context = propagator.from_headers({ + 'traceparent': + '00-12345678901234567890123456789012-1234567890123456-00-residue', + }) - def test_header_not_match(self): - header = '00-invalid_trace_id-66666-00' - trace_id = 'invalid_trace_id' + self.assertNotEqual(span_context.trace_id, trace_id) + def test_header_match(self): propagator = trace_context_http_header_format.\ TraceContextPropagator() - span_context = propagator.from_header(header) - self.assertNotEqual(span_context.trace_id, trace_id) + trace_id = '12345678901234567890123456789012' + span_id = '1234567890123456' - def test_headers_match(self): - # Trace option is enabled. - headers = { + # Trace option is not enabled. + span_context = propagator.from_headers({ 'traceparent': - '00-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7-01', - } - expected_trace_id = '6e0c63257de34c92bf9efcd03927272e' - expected_span_id = '00f067aa0ba902b7' - - propagator = trace_context_http_header_format.\ - TraceContextPropagator() - span_context = propagator.from_headers(headers) + '00-12345678901234567890123456789012-1234567890123456-00', + }) - self.assertEqual(span_context.trace_id, expected_trace_id) - self.assertEqual(span_context.span_id, expected_span_id) - self.assertTrue(span_context.trace_options.enabled) + self.assertEqual(span_context.trace_id, trace_id) + self.assertEqual(span_context.span_id, span_id) + self.assertFalse(span_context.trace_options.enabled) - def test_to_header(self): - from opencensus.trace import span_context - from opencensus.trace import trace_options + # Trace option is enabled. + span_context = propagator.from_headers({ + 'traceparent': + '00-12345678901234567890123456789012-1234567890123456-01', + }) - trace_id = '6e0c63257de34c92bf9efcd03927272e' - span_id_hex = '00f067aa0ba902b7' - span_context = span_context.SpanContext( - trace_id=trace_id, - span_id=span_id_hex, - trace_options=trace_options.TraceOptions('1')) + self.assertEqual(span_context.trace_id, trace_id) + self.assertEqual(span_context.span_id, span_id) + self.assertTrue(span_context.trace_options.enabled) + def test_header_not_match(self): propagator = trace_context_http_header_format.\ TraceContextPropagator() - header = propagator.to_header(span_context) - expected_header = '00-{}-{}-01'.format( - trace_id, - span_id_hex) + trace_id = 'invalid_trace_id' + span_context = propagator.from_headers({ + 'traceparent': + '00-invalid_trace_id-66666-00', + }) - self.assertEqual(header, expected_header) + self.assertNotEqual(span_context.trace_id, trace_id) def test_to_headers_without_tracestate(self): from opencensus.trace import span_context from opencensus.trace import trace_options - trace_id = '6e0c63257de34c92bf9efcd03927272e' - span_id_hex = '00f067aa0ba902b7' + propagator = trace_context_http_header_format.\ + TraceContextPropagator() + + trace_id = '12345678901234567890123456789012' + span_id_hex = '1234567890123456' span_context = span_context.SpanContext( trace_id=trace_id, span_id=span_id_hex, trace_options=trace_options.TraceOptions('1')) - propagator = trace_context_http_header_format.\ - TraceContextPropagator() - headers = propagator.to_headers(span_context) self.assertTrue('traceparent' in headers) @@ -193,17 +163,17 @@ def test_to_headers_with_empty_tracestate(self): from opencensus.trace import trace_options from opencensus.trace.tracestate import Tracestate - trace_id = '6e0c63257de34c92bf9efcd03927272e' - span_id_hex = '00f067aa0ba902b7' + propagator = trace_context_http_header_format.\ + TraceContextPropagator() + + trace_id = '12345678901234567890123456789012' + span_id_hex = '1234567890123456' span_context = span_context.SpanContext( trace_id=trace_id, span_id=span_id_hex, tracestate=Tracestate(), trace_options=trace_options.TraceOptions('1')) - propagator = trace_context_http_header_format.\ - TraceContextPropagator() - headers = propagator.to_headers(span_context) self.assertTrue('traceparent' in headers) @@ -217,17 +187,17 @@ def test_to_headers_with_tracestate(self): from opencensus.trace import trace_options from opencensus.trace.tracestate import Tracestate - trace_id = '6e0c63257de34c92bf9efcd03927272e' - span_id_hex = '00f067aa0ba902b7' + propagator = trace_context_http_header_format.\ + TraceContextPropagator() + + trace_id = '12345678901234567890123456789012' + span_id_hex = '1234567890123456' span_context = span_context.SpanContext( trace_id=trace_id, span_id=span_id_hex, tracestate=Tracestate(foo = "xyz"), trace_options=trace_options.TraceOptions('1')) - propagator = trace_context_http_header_format.\ - TraceContextPropagator() - headers = propagator.to_headers(span_context) self.assertTrue('traceparent' in headers) From 308b8162acf86444aa799ad1065a759ef98d4328 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Mon, 8 Oct 2018 21:15:37 -0700 Subject: [PATCH 3/4] improve code coverage --- .../trace_context_http_header_format.py | 15 +++------- .../test_trace_context_http_header_format.py | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/opencensus/trace/propagation/trace_context_http_header_format.py b/opencensus/trace/propagation/trace_context_http_header_format.py index 7bb091b96..25f47d232 100644 --- a/opencensus/trace/propagation/trace_context_http_header_format.py +++ b/opencensus/trace/propagation/trace_context_http_header_format.py @@ -22,9 +22,9 @@ _TRACEPARENT_HEADER_NAME = 'traceparent' _TRACESTATE_HEADER_NAME = 'tracestate' -_TRACE_CONTEXT_HEADER_FORMAT = \ +_TRACEPARENT_HEADER_FORMAT = \ '^[ \t]*([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(-.*)?[ \t]*$' -_TRACE_CONTEXT_HEADER_RE = re.compile(_TRACE_CONTEXT_HEADER_FORMAT) +_TRACEPARENT_HEADER_FORMAT_RE = re.compile(_TRACEPARENT_HEADER_FORMAT) class TraceContextPropagator(object): @@ -46,14 +46,7 @@ def from_headers(self, headers): if header is None: return SpanContext() - try: - match = re.search(_TRACE_CONTEXT_HEADER_RE, header) - except TypeError: - logging.warning( - 'Header should be str, got {}. Cannot parse the header.' - .format(header.__class__.__name__)) - return SpanContext() - + match = re.search(_TRACEPARENT_HEADER_FORMAT_RE, header) if not match: return SpanContext() @@ -68,7 +61,7 @@ def from_headers(self, headers): if version == '00': if match.group(5): return SpanContext() - elif version == 'ff': + if version == 'ff': return SpanContext() span_context = SpanContext( diff --git a/tests/unit/trace/propagation/test_trace_context_http_header_format.py b/tests/unit/trace/propagation/test_trace_context_http_header_format.py index 6b0825309..35aa5790b 100644 --- a/tests/unit/trace/propagation/test_trace_context_http_header_format.py +++ b/tests/unit/trace/propagation/test_trace_context_http_header_format.py @@ -54,6 +54,35 @@ def test_from_headers_with_tracestate(self): self.assertTrue(isinstance(span_context, SpanContext)) self.assertTrue(span_context.tracestate) + def test_from_headers_tracestate_limit(self): + propagator = trace_context_http_header_format.\ + TraceContextPropagator() + + span_context = propagator.from_headers({ + 'traceparent': + '00-12345678901234567890123456789012-1234567890123456-00', + 'tracestate': ','.join([ + 'a00=0,a01=1,a02=2,a03=3,a04=4,a05=5,a06=6,a07=7,a08=8,a09=9', + 'b00=0,b01=1,b02=2,b03=3,b04=4,b05=5,b06=6,b07=7,b08=8,b09=9', + 'c00=0,c01=1,c02=2,c03=3,c04=4,c05=5,c06=6,c07=7,c08=8,c09=9', + 'd00=0,d01=1,d02=2', + ]), + }) + + self.assertFalse(span_context.tracestate) + + def test_from_headers_tracestate_duplicated_keys(self): + propagator = trace_context_http_header_format.\ + TraceContextPropagator() + + span_context = propagator.from_headers({ + 'traceparent': + '00-12345678901234567890123456789012-1234567890123456-00', + 'tracestate': 'foo=1,bar=2,foo=3', + }) + + self.assertFalse(span_context.tracestate) + def test_header_all_zero(self): from opencensus.trace.span_context import SpanContext From bf3f5277b00422618ab3ccb4efd7f15581e4da99 Mon Sep 17 00:00:00 2001 From: Reiley Yang Date: Tue, 9 Oct 2018 08:32:44 -0700 Subject: [PATCH 4/4] fix lint --- .../trace/propagation/trace_context_http_header_format.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opencensus/trace/propagation/trace_context_http_header_format.py b/opencensus/trace/propagation/trace_context_http_header_format.py index 25f47d232..4b7afa100 100644 --- a/opencensus/trace/propagation/trace_context_http_header_format.py +++ b/opencensus/trace/propagation/trace_context_http_header_format.py @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging import re from opencensus.trace.span_context import SpanContext @@ -23,7 +22,8 @@ _TRACEPARENT_HEADER_NAME = 'traceparent' _TRACESTATE_HEADER_NAME = 'tracestate' _TRACEPARENT_HEADER_FORMAT = \ - '^[ \t]*([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(-.*)?[ \t]*$' + '^[ \t]*([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})' + \ + '(-.*)?[ \t]*$' _TRACEPARENT_HEADER_FORMAT_RE = re.compile(_TRACEPARENT_HEADER_FORMAT)