Inject and Extract
Introduction
In order to trace across process boundaries and RPC calls in distributed systems, spanContext
needs to propagated over the wire. The OpenTracing Python API provides two functions in the Tracer interface to do just that, inject(SpanContext, format, carrier)
and extract(format, carrier)
.
Format Options and Carriers
The format parameter refers to one of the three standard encodings the OpenTracing API defines:
TEXT_MAP
wherespanContext
is encoded as a collection of string key-value pairs,BINARY
wherespanContext
is encoded as an opaque byte array,HTTP_HEADERS
, which is similar toTEXT_MAP
except that the keys must be safe to be used as HTTP headers.
The carrier is an abstraction over the underlying RPC framework. For example, a carrier for TEXT_MAP
format is a dictionary, while a carrier for BINARY
format is a bytearray
.
Injecting and Extracting HTTP
When your service calls a downstream service, it’s useful to pass down the SpanContext
, so that Spans
generated by this service could join the Spans from our service in a single trace. To do that, our service needs to Inject
the SpanContext
into the payload. On the other side of the connection, the downstream service needs then to Extract
the SpanContext
before it creates any Spans
.
Injecting the spanContext
to HTTP
In order to pass a spanContext
over the HTTP request, the developer needs to call the tracer.inject
before building the HTTP request, like so:
from opentracing.ext import tags
from opentracing.propagation import Format
def http_get(port, path, param, value):
url = 'http://localhost:%s/%s' % (port, path)
span = get_current_span()
span.set_tag(tags.HTTP_METHOD, 'GET')
span.set_tag(tags.HTTP_URL, url)
span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
headers = {}
tracer.inject(span, Format.HTTP_HEADERS, headers)
r = requests.get(url, params={param: value}, headers=headers)
assert r.status_code == 200
return r.text
Notice that a couple additional tags have been added to the span
with some metadata about the HTTP request, following the OpenTracing Semantic Conventions.
Extracting the Span’s Context from Incoming Request
The logic on the client side instrumentation is similar, the only difference is that tracer.extract
is used and the span
is tagged as span.kind=server
.
@app.route("/format")
def format():
span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers)
span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
with tracer.start_span('format', child_of=span_ctx, tags=span_tags):
hello_to = request.args.get('helloTo')
return 'Hello, %s!' % hello_to
The complete tutorial is available here.
Injecting and Extracting TextMap
The process of injecting and extracting TextMap
is similar to that of HTTP. Given below are some elaborated code examples of injecting and extracting tracing information using TextMap
format.
def test_new_trace():
tracer = Tracer()
span = tracer.start_span(operation_name='test')
span.set_baggage_item('Fry', 'Leela')
span.set_tag('x', 'y')
span.log_event('z')
child = tracer.start_span(operation_name='child',
references=opentracing.child_of(span.context))
child.log_event('w')
carrier = {}
tracer.inject(
span_context=child.context, format=Format.TEXT_MAP, carrier=carrier)
child.finish()
span.finish()
The above function injects the child span’s spanContext
into TEXT_MAP
carrier.
def test_join_trace():
tracer = Tracer()
span_ctx = tracer.extract(format=Format.TEXT_MAP, carrier={})
span = tracer.start_span(operation_name='test',
references=opentracing.child_of(span_ctx))
span.set_tag('x', 'y')
span.set_baggage_item('a', 'b')
span.log_event('z')
span.finish()
Here, tracer.extract()
is used to extract the spanContext
from the carrier and start a new Span
with operation_name = ‘test’
. The logic is the reverse operation of the tracer.inject()
.
Injecting and Extracting BINARY
BINARY
format is used for injecting/extracting spanContext
encoded in an opaque byte array. Opaque means that the inner representation of the array is not known.
Injecting and extracting BINARY
can be implemented as follows:
#Inject binary
def inject_trace():
tracer = Tracer()
span = tracer.start_span()
bin_carrier = bytearray()
tracer.inject(
span_context=span.context,
format=Format.BINARY,
carrier=bin_carrier)
#Extract binary
def extract_trace():
tracer = Tracer()
noop_span = tracer._noop_span
bin_carrier = bytearray()
span_ctx = tracer.extract(Format.BINARY, carrier=bin_carrier)
Code example coming soon.