Skip to content
This repository was archived by the owner on Sep 17, 2025. It is now read-only.

Commit 2aef803

Browse files
authored
Improve W3C Trace Context compliance (#343)
* improve W3C TraceContext compliance * improve W3C TraceContext compliance * improve code coverage * fix lint
1 parent e72a4dc commit 2aef803

4 files changed

Lines changed: 151 additions & 181 deletions

File tree

opencensus/trace/propagation/trace_context_http_header_format.py

Lines changed: 47 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import logging
1615
import re
1716

1817
from opencensus.trace.span_context import SpanContext
@@ -22,64 +21,15 @@
2221

2322
_TRACEPARENT_HEADER_NAME = 'traceparent'
2423
_TRACESTATE_HEADER_NAME = 'tracestate'
25-
_TRACE_CONTEXT_HEADER_FORMAT = \
26-
'([0-9a-f]{2})(-([0-9a-f]{32}))(-([0-9a-f]{16}))?(-([0-9a-f]{2}))?'
27-
_TRACE_CONTEXT_HEADER_RE = re.compile(_TRACE_CONTEXT_HEADER_FORMAT)
24+
_TRACEPARENT_HEADER_FORMAT = \
25+
'^[ \t]*([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})' + \
26+
'(-.*)?[ \t]*$'
27+
_TRACEPARENT_HEADER_FORMAT_RE = re.compile(_TRACEPARENT_HEADER_FORMAT)
2828

2929

3030
class TraceContextPropagator(object):
3131
"""Propagator for processing the trace context HTTP header format."""
3232

33-
def from_header(self, header):
34-
"""Generate a SpanContext object using the trace context header.
35-
36-
:type header: str
37-
:param header: Trace context header which was extracted from the HTTP
38-
request headers.
39-
40-
:rtype: :class:`~opencensus.trace.span_context.SpanContext`
41-
:returns: SpanContext generated from the trace context header.
42-
"""
43-
if header is None:
44-
return SpanContext()
45-
46-
try:
47-
match = re.search(_TRACE_CONTEXT_HEADER_RE, header)
48-
except TypeError:
49-
logging.warning(
50-
'Header should be str, got {}. Cannot parse the header.'
51-
.format(header.__class__.__name__))
52-
raise
53-
54-
if match:
55-
version = match.group(1)
56-
57-
if version == '00':
58-
trace_id = match.group(3)
59-
span_id = match.group(5)
60-
trace_options = match.group(7)
61-
62-
if trace_options is None:
63-
trace_options = 1
64-
65-
# Need to convert span_id from hex string to int
66-
span_context = SpanContext(
67-
trace_id=trace_id,
68-
span_id=span_id,
69-
trace_options=TraceOptions(trace_options),
70-
from_header=True)
71-
return span_context
72-
else:
73-
logging.warning(
74-
'Header format version {} is not supported, generate a new'
75-
'context instead.'.format(version))
76-
else:
77-
logging.warning(
78-
'Cannot parse the header {}, generate a new context instead.'
79-
.format(header))
80-
81-
return SpanContext()
82-
8333
def from_headers(self, headers):
8434
"""Generate a SpanContext object using the W3C Distributed Tracing headers.
8535
@@ -91,42 +41,46 @@ def from_headers(self, headers):
9141
"""
9242
if headers is None:
9343
return SpanContext()
44+
9445
header = headers.get(_TRACEPARENT_HEADER_NAME)
9546
if header is None:
9647
return SpanContext()
97-
header = str(header.encode('utf-8'))
98-
99-
span_context = self.from_header(header)
10048

101-
header = headers.get(_TRACESTATE_HEADER_NAME)
102-
if header is not None:
103-
span_context.tracestate = \
104-
TracestateStringFormatter().from_string(header)
105-
106-
return span_context
49+
match = re.search(_TRACEPARENT_HEADER_FORMAT_RE, header)
50+
if not match:
51+
return SpanContext()
10752

108-
def to_header(self, span_context):
109-
"""Convert a SpanContext object to header string, using version 0.
53+
version = match.group(1)
54+
trace_id = match.group(2)
55+
span_id = match.group(3)
56+
trace_options = match.group(4)
11057

111-
:type span_context:
112-
:class:`~opencensus.trace.span_context.SpanContext`
113-
:param span_context: SpanContext object.
58+
if trace_id == '0' * 32 or span_id == '0' * 16:
59+
return SpanContext()
11460

115-
:rtype: str
116-
:returns: A trace context header string in trace context HTTP format.
117-
"""
118-
trace_id = span_context.trace_id
119-
span_id = span_context.span_id
120-
trace_options = span_context.trace_options.enabled
61+
if version == '00':
62+
if match.group(5):
63+
return SpanContext()
64+
if version == 'ff':
65+
return SpanContext()
12166

122-
# Convert the trace options
123-
trace_options = '01' if trace_options else '00'
67+
span_context = SpanContext(
68+
trace_id=trace_id,
69+
span_id=span_id,
70+
trace_options=TraceOptions(trace_options),
71+
from_header=True)
12472

125-
header = '00-{}-{}-{}'.format(
126-
trace_id,
127-
span_id,
128-
trace_options)
129-
return header
73+
header = headers.get(_TRACESTATE_HEADER_NAME)
74+
if header is None:
75+
return span_context
76+
try:
77+
tracestate = TracestateStringFormatter().from_string(header)
78+
if tracestate.is_valid():
79+
span_context.tracestate = \
80+
TracestateStringFormatter().from_string(header)
81+
except ValueError:
82+
pass
83+
return span_context
13084

13185
def to_headers(self, span_context):
13286
"""Convert a SpanContext object to W3C Distributed Tracing headers,
@@ -139,8 +93,19 @@ def to_headers(self, span_context):
13993
:rtype: dict
14094
:returns: W3C Distributed Tracing headers.
14195
"""
96+
trace_id = span_context.trace_id
97+
span_id = span_context.span_id
98+
trace_options = span_context.trace_options.enabled
99+
100+
# Convert the trace options
101+
trace_options = '01' if trace_options else '00'
102+
142103
headers = {
143-
_TRACEPARENT_HEADER_NAME: self.to_header(span_context),
104+
_TRACEPARENT_HEADER_NAME: '00-{}-{}-{}'.format(
105+
trace_id,
106+
span_id,
107+
trace_options
108+
),
144109
}
145110
tracestate = span_context.tracestate
146111
if tracestate:

opencensus/trace/propagation/tracestate_string_format.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ def from_string(self, string):
3232
if not match:
3333
raise ValueError('illegal key-value format %r' % (member))
3434
key, eq, value = match.groups()
35+
if key in tracestate:
36+
raise ValueError('conflict key {!r}'.format(key))
3537
tracestate[key] = value
3638
return tracestate
3739

opencensus/trace/tracestate.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@
1515
from collections import OrderedDict
1616
import re
1717

18-
_KEY_FORMAT = r'[a-z][_0-9a-z\-\*\/]{0,255}'
19-
_VALUE_FORMAT = r'[\x20-\x2b\x2d-\x3c\x3e-\x7e]{1,256}'
18+
_KEY_WITHOUT_VENDOR_FORMAT = r'[a-z][_0-9a-z\-\*\/]{0,255}'
19+
_KEY_WITH_VENDOR_FORMAT = \
20+
r'[a-z][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}'
21+
_KEY_FORMAT = _KEY_WITHOUT_VENDOR_FORMAT + '|' + _KEY_WITH_VENDOR_FORMAT
22+
_VALUE_FORMAT = \
23+
r'[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]'
2024

2125
_KEY_VALIDATION_RE = re.compile('^' + _KEY_FORMAT + '$')
2226
_VALUE_VALIDATION_RE = re.compile('^' + _VALUE_FORMAT + '$')

0 commit comments

Comments
 (0)