1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
|
OpenTracing API for Python
==========================
|GitterChat| |BuildStatus| |PyPI| |ReadTheDocs|
This library is a Python platform API for OpenTracing.
Required Reading
----------------
In order to understand the Python platform API, one must first be familiar with
the `OpenTracing project <http://opentracing.io>`_ and
`terminology <http://opentracing.io/documentation/pages/spec.html>`_ more
specifically.
Status
------
In the current version, ``opentracing-python`` provides only the API and a
basic no-op implementation that can be used by instrumentation libraries to
collect and propagate distributed tracing context.
Future versions will include a reference implementation utilizing an
abstract Recorder interface, as well as a
`Zipkin <http://openzipkin.github.io>`_-compatible Tracer.
Usage
-----
The work of instrumentation libraries generally consists of three steps:
1. When a service receives a new request (over HTTP or some other protocol),
it uses OpenTracing's inject/extract API to continue an active trace, creating a
Span object in the process. If the request does not contain an active trace,
the service starts a new trace and a new *root* Span.
2. The service needs to store the current Span in some request-local storage,
(called ``Span`` *activation*) where it can be retrieved from when a child Span must
be created, e.g. in case of the service making an RPC to another service.
3. When making outbound calls to another service, the current Span must be
retrieved from request-local storage, a child span must be created (e.g., by
using the ``start_child_span()`` helper), and that child span must be embedded
into the outbound request (e.g., using HTTP headers) via OpenTracing's
inject/extract API.
Below are the code examples for the previously mentioned steps. Implementation
of request-local storage needed for step 2 is specific to the service and/or frameworks /
instrumentation libraries it is using, exposed as a ``ScopeManager`` child contained
as ``Tracer.scope_manager``. See details below.
Inbound request
^^^^^^^^^^^^^^^
Somewhere in your server's request handler code:
.. code-block:: python
def handle_request(request):
span = before_request(request, opentracing.global_tracer())
# store span in some request-local storage using Tracer.scope_manager,
# using the returned `Scope` as Context Manager to ensure
# `Span` will be cleared and (in this case) `Span.finish()` be called.
with tracer.scope_manager.activate(span, True) as scope:
# actual business logic
handle_request_for_real(request)
def before_request(request, tracer):
span_context = tracer.extract(
format=Format.HTTP_HEADERS,
carrier=request.headers,
)
span = tracer.start_span(
operation_name=request.operation,
child_of=span_context)
span.set_tag('http.url', request.full_url)
remote_ip = request.remote_ip
if remote_ip:
span.set_tag(tags.PEER_HOST_IPV4, remote_ip)
caller_name = request.caller_name
if caller_name:
span.set_tag(tags.PEER_SERVICE, caller_name)
remote_port = request.remote_port
if remote_port:
span.set_tag(tags.PEER_PORT, remote_port)
return span
Outbound request
----------------
Somewhere in your service that's about to make an outgoing call:
.. code-block:: python
from opentracing import tags
from opentracing.propagation import Format
from opentracing_instrumentation import request_context
# create and serialize a child span and use it as context manager
with before_http_request(
request=out_request,
current_span_extractor=request_context.get_current_span):
# actual call
return urllib2.urlopen(request)
def before_http_request(request, current_span_extractor):
op = request.operation
parent_span = current_span_extractor()
outbound_span = opentracing.global_tracer().start_span(
operation_name=op,
child_of=parent_span
)
outbound_span.set_tag('http.url', request.full_url)
service_name = request.service_name
host, port = request.host_port
if service_name:
outbound_span.set_tag(tags.PEER_SERVICE, service_name)
if host:
outbound_span.set_tag(tags.PEER_HOST_IPV4, host)
if port:
outbound_span.set_tag(tags.PEER_PORT, port)
http_header_carrier = {}
opentracing.global_tracer().inject(
span_context=outbound_span,
format=Format.HTTP_HEADERS,
carrier=http_header_carrier)
for key, value in http_header_carrier.iteritems():
request.add_header(key, value)
return outbound_span
Scope and within-process propagation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For getting/setting the current active ``Span`` in the used request-local storage,
OpenTracing requires that every ``Tracer`` contains a ``ScopeManager`` that grants
access to the active ``Span`` through a ``Scope``. Any ``Span`` may be transferred to
another task or thread, but not ``Scope``.
.. code-block:: python
# Access to the active span is straightforward.
scope = tracer.scope_manager.active()
if scope is not None:
scope.span.set_tag('...', '...')
The common case starts a ``Scope`` that's automatically registered for intra-process
propagation via ``ScopeManager``.
Note that ``start_active_span('...')`` automatically finishes the span on ``Scope.close()``
(``start_active_span('...', finish_on_close=False)`` does not finish it, in contrast).
.. code-block:: python
# Manual activation of the Span.
span = tracer.start_span(operation_name='someWork')
with tracer.scope_manager.activate(span, True) as scope:
# Do things.
# Automatic activation of the Span.
# finish_on_close is a required parameter.
with tracer.start_active_span('someWork', finish_on_close=True) as scope:
# Do things.
# Handling done through a try construct:
span = tracer.start_span(operation_name='someWork')
scope = tracer.scope_manager.activate(span, True)
try:
# Do things.
except Exception as e:
span.set_tag('error', '...')
finally:
scope.close()
**If there is a Scope, it will act as the parent to any newly started Span** unless
the programmer passes ``ignore_active_span=True`` at ``start_span()``/``start_active_span()``
time or specified parent context explicitly:
.. code-block:: python
scope = tracer.start_active_span('someWork', ignore_active_span=True)
Each service/framework ought to provide a specific ``ScopeManager`` implementation
that relies on their own request-local storage (thread-local storage, or coroutine-based storage
for asynchronous frameworks, for example).
Scope managers
^^^^^^^^^^^^^^
This project includes a set of ``ScopeManager`` implementations under the ``opentracing.scope_managers`` submodule, which can be imported on demand:
.. code-block:: python
from opentracing.scope_managers import ThreadLocalScopeManager
There exist implementations for ``thread-local`` (the default instance of the submodule ``opentracing.scope_managers``), ``gevent``, ``Tornado``, ``asyncio`` and ``contextvars``:
.. code-block:: python
from opentracing.scope_managers.gevent import GeventScopeManager # requires gevent
from opentracing.scope_managers.tornado import TornadoScopeManager # requires tornado<6
from opentracing.scope_managers.asyncio import AsyncioScopeManager # fits for old asyncio applications, requires Python 3.4 or newer.
from opentracing.scope_managers.contextvars import ContextVarsScopeManager # for asyncio applications, requires Python 3.7 or newer.
**Note** that for asyncio applications it's preferable to use ``ContextVarsScopeManager`` instead of ``AsyncioScopeManager`` because of automatic parent span propagation to children coroutines, tasks or scheduled callbacks.
Development
-----------
Tests
^^^^^
.. code-block:: sh
virtualenv env
. ./env/bin/activate
make bootstrap
make test
You can use `tox <https://tox.readthedocs.io>`_ to run tests as well.
.. code-block:: sh
tox
Testbed suite
^^^^^^^^^^^^^
A testbed suite designed to test API changes and experimental features is included under the *testbed* directory. For more information, see the `Testbed README <testbed/README.md>`_.
Instrumentation Tests
---------------------
This project has a working design of interfaces for the OpenTracing API. There is a MockTracer to
facilitate unit-testing of OpenTracing Python instrumentation.
.. code-block:: python
from opentracing.mocktracer import MockTracer
tracer = MockTracer()
with tracer.start_span('someWork') as span:
pass
spans = tracer.finished_spans()
someWorkSpan = spans[0]
Documentation
^^^^^^^^^^^^^
.. code-block:: sh
virtualenv env
. ./env/bin/activate
make bootstrap
make docs
The documentation is written to *docs/_build/html*.
LICENSE
^^^^^^^
`Apache 2.0 License <./LICENSE>`__.
Releases
^^^^^^^^
Before new release, add a summary of changes since last version to CHANGELOG.rst
.. code-block:: sh
pip install zest.releaser[recommended]
prerelease
release
git push origin master --follow-tags
python setup.py sdist upload -r pypi upload_docs -r pypi
postrelease
git push
.. |GitterChat| replace:: Gitter chat
:target: https://gitter.im/opentracing/public
.. |BuildStatus| replace:: Travis build status
:target: https://travis-ci.org/opentracing/opentracing-python
.. |PyPI| replace:: PyPI
:target: https://badge.fury.io/py/opentracing
.. |ReadTheDocs| replace:: ReadTheDocs
:target: https://opentracing-python.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
|