Skip to content
This repository was archived by the owner on Jul 11, 2022. It is now read-only.

Commit 2533be7

Browse files
cedyyurishkuro
andauthored
Implement Binary codec (#304)
* Implement Binary codec Implements extract and inject methods of the Binary codec to be able to inject/extract span context to/from bytearray. Signed-off-by: George Leman <[email protected]> * Add test for binary codec compatibility with goclient Signed-off-by: George Leman <[email protected]> * Remove extra baggage items from binary codec test Signed-off-by: George Leman <[email protected]> Co-authored-by: Yuri Shkuro <[email protected]>
1 parent 1cb8e52 commit 2533be7

File tree

2 files changed

+157
-5
lines changed

2 files changed

+157
-5
lines changed

jaeger_client/codecs.py

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
from __future__ import absolute_import
1616

17+
import struct
18+
1719
from opentracing import (
1820
InvalidCarrierException,
1921
SpanContextCorruptedException,
@@ -129,19 +131,78 @@ def _parse_baggage_header(self, header, baggage):
129131

130132
class BinaryCodec(Codec):
131133
"""
132-
BinaryCodec is a no-op.
133-
134+
Implements inject/extract of SpanContext to/from binary that compatible with golang
135+
implementation
136+
https://github.com/jaegertracing/jaeger-client-go/blob/master/propagation.go#L177-L290
137+
Supports propagation of trace_id, span_id, flags and baggage
134138
"""
135139
def inject(self, span_context, carrier):
136140
if not isinstance(carrier, bytearray):
137141
raise InvalidCarrierException('carrier not a bytearray')
138-
pass # TODO binary encoding not implemented
142+
# check if we have 128 bit trace_id, break it into two 64 units
143+
max_int64 = 0xFFFFFFFFFFFFFFFF
144+
if span_context.trace_id > max_int64:
145+
high = (span_context.trace_id >> 64) & max_int64
146+
low = span_context.trace_id & max_int64
147+
else:
148+
high = 0
149+
low = span_context.trace_id
150+
carrier += struct.pack('>QQQQBI', high, low, span_context.span_id or 0,
151+
span_context.parent_id or 0, span_context.flags,
152+
len(span_context.baggage))
153+
154+
for k, v in span_context.baggage.items():
155+
carrier += self._pack_baggage_item(k, v)
139156

140157
def extract(self, carrier):
141158
if not isinstance(carrier, bytearray):
142159
raise InvalidCarrierException('carrier not a bytearray')
143-
# TODO binary encoding not implemented
144-
return None
160+
baggage = {}
161+
high_trace_id, low_trace_id, span_id, parent_id, flags, baggage_count = \
162+
struct.unpack('>QQQQBI', carrier[:37])
163+
# if high_trace_id isn't 0, then we are dealing with 128bit trace id integer,
164+
# therefore unpack into 1 number
165+
if high_trace_id:
166+
trace_id = (high_trace_id << 64) | low_trace_id
167+
else:
168+
trace_id = low_trace_id
169+
170+
if baggage_count != 0:
171+
baggage_data = carrier[37:]
172+
for _ in range(baggage_count):
173+
key, value, bytes_read = self._unpack_baggage_item(baggage_data)
174+
baggage[key] = value
175+
baggage_data = baggage_data[bytes_read:]
176+
177+
return SpanContext(trace_id=trace_id, span_id=span_id,
178+
parent_id=parent_id, flags=flags, baggage=baggage)
179+
180+
def _pack_baggage_item(self, key, value):
181+
baggage = bytearray()
182+
if not isinstance(key, bytes):
183+
key = key.encode('utf-8')
184+
baggage += struct.pack('>I', len(key))
185+
baggage += key
186+
187+
if not isinstance(value, bytes):
188+
value = value.encode('utf-8')
189+
baggage += struct.pack('>I', len(value))
190+
baggage += value
191+
return baggage
192+
193+
def _unpack_baggage_item(self, baggage):
194+
bytes_read = 0
195+
key, b_read = self._read_kv(baggage)
196+
bytes_read += b_read
197+
value, b_read = self._read_kv(baggage[bytes_read:])
198+
bytes_read += b_read
199+
return key, value, bytes_read
200+
201+
def _read_kv(self, data):
202+
data_len = struct.unpack('>i', data[:4])[0]
203+
data_value = struct.unpack('>' + 'c' * data_len, data[4:4 + data_len])
204+
bytes_read = 4 + data_len
205+
return b''.join(data_value).decode('utf-8'), bytes_read
145206

146207

147208
def span_context_to_string(trace_id, span_id, parent_id, flags):

tests/test_codecs.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,97 @@ def test_binary_codec(self):
384384
with self.assertRaises(InvalidCarrierException):
385385
codec.extract({})
386386

387+
tracer = Tracer(
388+
service_name='test',
389+
reporter=InMemoryReporter(),
390+
sampler=ConstSampler(True),
391+
)
392+
baggage = {'baggage_1': u'data',
393+
u'baggage_2': 'foobar',
394+
'baggage_3': '\x00\x01\x09\xff',
395+
u'baggage_4': u'\U0001F47E'}
396+
397+
span_context = SpanContext(trace_id=260817200211625699950706086749966912306, span_id=567890,
398+
parent_id=1234567890, flags=1,
399+
baggage=baggage)
400+
401+
carrier = bytearray()
402+
tracer.inject(span_context, Format.BINARY, carrier)
403+
assert len(carrier) != 0
404+
405+
extracted_span_context = tracer.extract(Format.BINARY, carrier)
406+
assert extracted_span_context.trace_id == span_context.trace_id
407+
assert extracted_span_context.span_id == span_context.span_id
408+
assert extracted_span_context.parent_id == span_context.parent_id
409+
assert extracted_span_context.flags == span_context.flags
410+
assert extracted_span_context.baggage == span_context.baggage
411+
412+
def test_binary_codec_extract_compatibility_with_golang_client(self):
413+
tracer = Tracer(
414+
service_name='test',
415+
reporter=InMemoryReporter(),
416+
sampler=ConstSampler(True),
417+
)
418+
tests = {
419+
b'\x00\x00\x00\x00\x00\x00\x00\x00u\x18\xa9\x13\xa0\xd2\xaf4u\x18\xa9\x13\xa0\xd2\xaf4'
420+
b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00':
421+
{'trace_id_high': 0,
422+
'trace_id_low': 8437679803646258996,
423+
'span_id': 8437679803646258996,
424+
'parent_id': None,
425+
'flags': 1,
426+
'baggage_count': 0,
427+
'baggage': {}},
428+
b'K2\x88\x8b\x8f\xb5\x96\xe9+\xc6\xe6\xf5\x9d\xed\x8a\xd0+\xc6\xe6\xf5\x9d\xed\x8a\xd0'
429+
b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00':
430+
{'trace_id_high': 5418543434673002217,
431+
'trace_id_low': 3154462531610577616,
432+
'span_id': 3154462531610577616,
433+
'parent_id': None,
434+
'flags': 1,
435+
'baggage_count': 0,
436+
'baggage': {}},
437+
b'd\xb7^Y\x1afI\x0bi\xe4lc`\x1e\xbep[\x0fw\xc8\x87\xfd\xb2Ti\xe4lc`\x1e\xbep\x01\x00'
438+
b'\x00\x00\x00':
439+
{'trace_id_high': 7257373061318854923,
440+
'trace_id_low': 7630342842742652528,
441+
'span_id': 6561594885260816980,
442+
'parent_id': 7630342842742652528,
443+
'flags': 1,
444+
'baggage_count': 0,
445+
'baggage': {}},
446+
b'a]\x85\xe0\xe0\x06\xd5[6k\x9d\x86\xaa\xbc\\\x8f#c\x06\x80jV\xdf\x826k\x9d\x86\xaa\xbc'
447+
b'\\\x8f\x01\x00\x00\x00\x01\x00\x00\x00\x07key_one\x00\x00\x00\tvalue_one':
448+
{'trace_id_high': 7015910995390813531,
449+
'trace_id_low': 3921401102271798415,
450+
'span_id': 2549888962631491458,
451+
'parent_id': 3921401102271798415,
452+
'flags': 1,
453+
'baggage_count': 1,
454+
'baggage': {'key_one': 'value_one'}
455+
},
456+
}
457+
458+
for span_context_serialized, expected in tests.items():
459+
span_context = tracer.extract(Format.BINARY, bytearray(span_context_serialized))
460+
# because python supports 128bit number as one number and go splits it in two 64 bit
461+
# numbers, we need to split python number to compare it properly to go implementation
462+
max_int64 = 0xFFFFFFFFFFFFFFFF
463+
trace_id_high = (span_context.trace_id >> 64) & max_int64
464+
trace_id_low = span_context.trace_id & max_int64
465+
466+
assert trace_id_high == expected['trace_id_high']
467+
assert trace_id_low == expected['trace_id_low']
468+
assert span_context.span_id == expected['span_id']
469+
assert span_context.parent_id == expected['parent_id']
470+
assert span_context.flags == expected['flags']
471+
assert len(span_context.baggage) == expected['baggage_count']
472+
assert span_context.baggage == expected['baggage']
473+
474+
carrier = bytearray()
475+
tracer.inject(span_context, Format.BINARY, carrier)
476+
assert carrier == bytearray(span_context_serialized)
477+
387478

388479
def test_default_baggage_without_trace_id(tracer):
389480
_test_baggage_without_trace_id(

0 commit comments

Comments
 (0)