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

Commit bece5d9

Browse files
authored
Add TraceContextPropagator (#49)
1 parent 99d3bbe commit bece5d9

2 files changed

Lines changed: 223 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import logging
16+
import re
17+
18+
from opencensus.trace.span_context import SpanContext
19+
from opencensus.trace.trace_options import TraceOptions
20+
21+
_TRACE_CONTEXT_HEADER_FORMAT = \
22+
'([0-9a-f]{2})(-([0-9a-f]{32}))(-([0-9a-f]{16}))?(-([0-9a-f]{2}))?'
23+
_TRACE_CONTEXT_HEADER_RE = re.compile(_TRACE_CONTEXT_HEADER_FORMAT)
24+
25+
26+
class TraceContextPropagator(object):
27+
28+
def from_header(self, header):
29+
"""Generate a SpanContext object using the trace context header.
30+
31+
:type header: str
32+
:param header: Trace context header which was extracted from the HTTP
33+
request headers.
34+
35+
:rtype: :class:`~opencensus.trace.span_context.SpanContext`
36+
:returns: SpanContext generated from the trace context header.
37+
"""
38+
if header is None:
39+
return SpanContext()
40+
41+
try:
42+
match = re.search(_TRACE_CONTEXT_HEADER_RE, header)
43+
except TypeError:
44+
logging.warning(
45+
'Header should be str, got {}. Cannot parse the header.'
46+
.format(header.__class__.__name__))
47+
raise
48+
49+
if match:
50+
version = match.group(1)
51+
52+
if version == '00':
53+
trace_id = match.group(3)
54+
span_id = match.group(5)
55+
trace_options = match.group(7)
56+
57+
if trace_options is None:
58+
trace_options = 1
59+
60+
# Need to convert span_id from hex string to int
61+
span_context = SpanContext(
62+
trace_id=trace_id,
63+
span_id=int(span_id, 16),
64+
trace_options=TraceOptions(trace_options),
65+
from_header=True)
66+
return span_context
67+
else:
68+
logging.warning(
69+
'Header format version {} is not supported, generate a new'
70+
'context instead.'.format(version))
71+
else:
72+
logging.warning(
73+
'Cannot parse the header {}, generate a new context instead.'
74+
.format(header))
75+
76+
return SpanContext()
77+
78+
def to_header(self, span_context):
79+
"""Convert a SpanContext object to header string, using version 0.
80+
81+
:type span_context:
82+
:class:`~opencensus.trace.span_context.SpanContext`
83+
:param span_context: SpanContext object.
84+
85+
:rtype: str
86+
:returns: A trace context header string in trace context HTTP format.
87+
"""
88+
trace_id = span_context.trace_id
89+
span_id = span_context.span_id
90+
trace_options = span_context.trace_options.enabled
91+
92+
# Need to convert span_id from int to hex string
93+
span_id_hex = hex(span_id)
94+
span_id = span_id_hex[2:].zfill(16)
95+
96+
# Convert the trace options
97+
trace_options = '01' if trace_options else '00'
98+
99+
header = '00-{}-{}-{}'.format(
100+
trace_id,
101+
span_id,
102+
trace_options)
103+
return header
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
# Copyright 2017, OpenCensus Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import unittest
16+
17+
from opencensus.trace.propagation import trace_context_http_header_format
18+
19+
20+
class TestTraceContextPropagator(unittest.TestCase):
21+
22+
def test_from_header_no_header(self):
23+
from opencensus.trace.span_context import SpanContext
24+
25+
propagator = trace_context_http_header_format.\
26+
TraceContextPropagator()
27+
span_context = propagator.from_header(None)
28+
29+
assert isinstance(span_context, SpanContext)
30+
31+
def test_header_type_error(self):
32+
header = 1234
33+
34+
propagator = trace_context_http_header_format.\
35+
TraceContextPropagator()
36+
37+
with self.assertRaises(TypeError):
38+
propagator.from_header(header)
39+
40+
def test_header_version_not_support(self):
41+
from opencensus.trace.span_context import SpanContext
42+
43+
header = '01-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7-00'
44+
propagator = trace_context_http_header_format. \
45+
TraceContextPropagator()
46+
span_context = propagator.from_header(header)
47+
48+
assert isinstance(span_context, SpanContext)
49+
50+
def test_header_match(self):
51+
# Trace option is not enabled.
52+
header = '00-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7-00'
53+
expected_trace_id = '6e0c63257de34c92bf9efcd03927272e'
54+
expected_span_id = 67667974448284343
55+
56+
propagator = trace_context_http_header_format.\
57+
TraceContextPropagator()
58+
span_context = propagator.from_header(header)
59+
60+
self.assertEqual(span_context.trace_id, expected_trace_id)
61+
self.assertEqual(span_context.span_id, expected_span_id)
62+
self.assertFalse(span_context.trace_options.enabled)
63+
64+
# Trace option is enabled.
65+
header = '00-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7-01'
66+
expected_trace_id = '6e0c63257de34c92bf9efcd03927272e'
67+
expected_span_id = 67667974448284343
68+
69+
propagator = trace_context_http_header_format.\
70+
TraceContextPropagator()
71+
span_context = propagator.from_header(header)
72+
73+
self.assertEqual(span_context.trace_id, expected_trace_id)
74+
self.assertEqual(span_context.span_id, expected_span_id)
75+
self.assertTrue(span_context.trace_options.enabled)
76+
77+
def test_header_match_no_option(self):
78+
header = '00-6e0c63257de34c92bf9efcd03927272e-00f067aa0ba902b7'
79+
expected_trace_id = '6e0c63257de34c92bf9efcd03927272e'
80+
expected_span_id = 67667974448284343
81+
82+
propagator = trace_context_http_header_format.\
83+
TraceContextPropagator()
84+
span_context = propagator.from_header(header)
85+
86+
self.assertEqual(span_context.trace_id, expected_trace_id)
87+
self.assertEqual(span_context.span_id, expected_span_id)
88+
self.assertTrue(span_context.trace_options.enabled)
89+
90+
def test_header_not_match(self):
91+
header = '00-invalid_trace_id-66666-00'
92+
trace_id = 'invalid_trace_id'
93+
94+
propagator = trace_context_http_header_format.\
95+
TraceContextPropagator()
96+
span_context = propagator.from_header(header)
97+
98+
self.assertNotEqual(span_context.trace_id, trace_id)
99+
100+
def test_to_header(self):
101+
from opencensus.trace import span_context
102+
from opencensus.trace import trace_options
103+
104+
trace_id = '6e0c63257de34c92bf9efcd03927272e'
105+
span_id = 67667974448284343
106+
span_id_hex = '00f067aa0ba902b7'
107+
span_context = span_context.SpanContext(
108+
trace_id=trace_id,
109+
span_id=span_id,
110+
trace_options=trace_options.TraceOptions('1'))
111+
112+
propagator = trace_context_http_header_format.\
113+
TraceContextPropagator()
114+
115+
header = propagator.to_header(span_context)
116+
expected_header = '00-{}-{}-01'.format(
117+
trace_id,
118+
span_id_hex)
119+
120+
self.assertEqual(header, expected_header)

0 commit comments

Comments
 (0)