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:

  1. TEXT_MAP where spanContext is encoded as a collection of string key-value pairs,
  2. BINARY where spanContext is encoded as an opaque byte array,
  3. HTTP_HEADERS, which is similar to TEXT_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:

  1. from opentracing.ext import tags
  2. from opentracing.propagation import Format
  3. def http_get(port, path, param, value):
  4. url = 'http://localhost:%s/%s' % (port, path)
  5. span = get_current_span()
  6. span.set_tag(tags.HTTP_METHOD, 'GET')
  7. span.set_tag(tags.HTTP_URL, url)
  8. span.set_tag(tags.SPAN_KIND, tags.SPAN_KIND_RPC_CLIENT)
  9. headers = {}
  10. tracer.inject(span, Format.HTTP_HEADERS, headers)
  11. r = requests.get(url, params={param: value}, headers=headers)
  12. assert r.status_code == 200
  13. 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.

  1. @app.route("/format")
  2. def format():
  3. span_ctx = tracer.extract(Format.HTTP_HEADERS, request.headers)
  4. span_tags = {tags.SPAN_KIND: tags.SPAN_KIND_RPC_SERVER}
  5. with tracer.start_span('format', child_of=span_ctx, tags=span_tags):
  6. hello_to = request.args.get('helloTo')
  7. 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.

  1. def test_new_trace():
  2. tracer = Tracer()
  3. span = tracer.start_span(operation_name='test')
  4. span.set_baggage_item('Fry', 'Leela')
  5. span.set_tag('x', 'y')
  6. span.log_event('z')
  7. child = tracer.start_span(operation_name='child',
  8. references=opentracing.child_of(span.context))
  9. child.log_event('w')
  10. carrier = {}
  11. tracer.inject(
  12. span_context=child.context, format=Format.TEXT_MAP, carrier=carrier)
  13. child.finish()
  14. span.finish()

The above function injects the child span’s spanContext into TEXT_MAP carrier.

  1. def test_join_trace():
  2. tracer = Tracer()
  3. span_ctx = tracer.extract(format=Format.TEXT_MAP, carrier={})
  4. span = tracer.start_span(operation_name='test',
  5. references=opentracing.child_of(span_ctx))
  6. span.set_tag('x', 'y')
  7. span.set_baggage_item('a', 'b')
  8. span.log_event('z')
  9. 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:

  1. #Inject binary
  2. def inject_trace():
  3. tracer = Tracer()
  4. span = tracer.start_span()
  5. bin_carrier = bytearray()
  6. tracer.inject(
  7. span_context=span.context,
  8. format=Format.BINARY,
  9. carrier=bin_carrier)
  1. #Extract binary
  2. def extract_trace():
  3. tracer = Tracer()
  4. noop_span = tracer._noop_span
  5. bin_carrier = bytearray()
  6. span_ctx = tracer.extract(Format.BINARY, carrier=bin_carrier)

Code example coming soon.