OpenTracing API for Python

Overview

OpenTracing API for Python

GitterChat BuildStatus PyPI Documentation Status

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 and terminology 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-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:

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:

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.context,
        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.

# 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).

# 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:

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:

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:

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

virtualenv env
. ./env/bin/activate
make bootstrap
make test

You can use tox to run tests as well.

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.

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.

from opentracing.mocktracer import MockTracer

tracer = MockTracer()
with tracer.start_span('someWork') as span:
    pass

spans = tracer.finished_spans()
someWorkSpan = spans[0]

Documentation

virtualenv env
. ./env/bin/activate
make bootstrap
make docs

The documentation is written to docs/_build/html.

LICENSE

Apache 2.0 License.

Releases

Before new release, add a summary of changes since last version to CHANGELOG.rst

pip install 'zest.releaser[recommended]'
prerelease
release
git push origin master --follow-tags
make docs
python setup.py sdist upload -r pypi upload_docs -r pypi
postrelease
git push
Comments
  • 2.0.0 Release

    2.0.0 Release

    This issue tracks the push for releasing the new 2.0.0 API for OpenTracing-Python.

    Based on the Java 0.31 API, the next version of the Python edition of OpenTracing is now available and ready for testing.

    RC Branch: https://github.com/opentracing/opentracing-python/tree/v2.0.0

    Current Status

    • The Scope concept has been brought from the Java API, and is being validated.
    • Tracer.start_active_span() defaults finish_on_close=True, after feedback received for the first Release Candidate.
    • start_active_span has been defined to have operation_name as a required parameter, as opposed to how it's handled in start_span.
    • Properties have been used for new members (such as Scope.span), following the current style (Span.context, for example).
    • Imported a testbed suite, which includes a few instrumentation patterns, so API changes and experimental features can be tested out there. This is the equivalent to opentracing-testbed for the Java API.
    • Documentation may need to be re-reviewed and extended.
    • basictracer-python has been updated as well.

    Release process

    • The new API is being developed on the v2.0.0 branch.
    • Revisions should be requested as Pull Requests against this branch.
    • master will continue to point at the latest patch version of the v2.0.0 branch, until 2.0.0 is officially released.
    • Changes to the release candidate will be released on PyPi as 2.0.0.rc1, 2.0.0.rc2, etc.

    Current RC snapshots

    • https://pypi.python.org/pypi/opentracing/2.0.0rc2
    • https://pypi.python.org/pypi/basictracer/3.0.0rc2
    opened by carlosalberto 34
  • AsyncioScopeManager based on contextvars and supporting Tornado 6

    AsyncioScopeManager based on contextvars and supporting Tornado 6

    Python 3.7 provides contextvars that can be used for implicitly propagation of context from parent to child coroutines / tasks. Implementation of AsyncioScopeManager in the PR based on this feature so no more need to make manual activation of parent span in child coroutine / tasks like this:

    with tracer.scope_manager.activate(parent_span, finish_on_close=False):
                    ...
                    with tracer.start_active_span('child') as scope:
                        ...
    

    For python 3.6 we have backport of contextvars with async / await supporting https://github.com/fantix/aiocontextvars that based on the "official" backport https://github.com/MagicStack/contextvars. So in python 3.6 "new" AsyncioScopeManager also works.

    Testbed contains tests for "new" AsyncioScopeManager and compatibility tests for original scope manager.

    On the other side Tornado >= 6 became fully asyncio-based framework (that why stack_context was deprecated) so we no need to support TornadoScopeManager for these versions. I think the better way is to use AsyncioScopeManager in new Tornado applications instead of backporting stack_context or something like this in TornadoScopeManager specially for opentracing needs. Of course for Tornado < 6 we have to use TornadoScopeManager and it still here.

    opened by condorcet 22
  • Implementation of ScopeManager for event loop frameworks

    Implementation of ScopeManager for event loop frameworks

    @carlosalberto I finally had a chance to look at the new Scope API. It looks fine, but what about the implementations? There was a PR almost a year ago that showed how the API works with different async frameworks like Tornado, gevent, etc.

    opened by yurishkuro 20
  • Fix some v2.0.0 docs

    Fix some v2.0.0 docs

    Building (and reading through) the documentation revealed a number of issues:

    ...opentracing.Tracer.start_active_span:8: WARNING: Definition list ends without a blank line; unexpected unindent.
    ...opentracing.Tracer.start_active_span:16: WARNING: Block quote ends without a blank line; unexpected unindent.
    ...opentracing.Tracer.start_active_span:18: WARNING: Definition list ends without a blank line; unexpected unindent.
    ../CHANGELOG.rst:30: WARNING: Inline strong start-string without end-string.
    ../CHANGELOG.rst:118: WARNING: Bullet list ends without a blank line; unexpected unindent.
    ...
    copying static files... WARNING: html_static_path entry u'/Users/kyle.verhoog/dev/ot-py-kyle/docs/_static' does not exist
    ...
    build succeeded, 6 warnings.
    

    This PR fixes these warnings.

    This PR also attempts to:

    • Use :meth:, :attr:, :class: and :exc: where possible to make navigating the docs much easier.
    • Convert mentions of instances of classes to lower case
    • Use single backticks for argument names
    • Use double backticks for inline code and operators
    • Add argument types

    @palazzem

    opened by Kyle-Verhoog 19
  • Merge Tracer.join_trace into Tracer.start_trace

    Merge Tracer.join_trace into Tracer.start_trace

    A PR to discuss merging start_trace, join_trace, and child_span as discussed in https://github.com/opentracing/opentracing.github.io/pull/34 and https://github.com/opentracing/opentracing-java/pull/7

    The PR only merges start_trace and join_trace. This results in the following deduplication:

    if context is None:
         span = tracer.start_trace(operation_name=operation)
    else:
        span = tracer.join_trace(operation_name=operation,
                                 parent_trace_context=context)
    

    to

        span = tracer.start_trace(operation_name=operation,
                                 parent_trace_context=context)
    

    child_span was also mentioned for possible consolidation, but on examination, it seemed to stand well on its own. child_span has a different use-case: continuing a trace within a process, rather than starting/continuing at a service entry point. I think these two should be differentiated for tracers which might handle sampling, propagation, or reporting differently at service entry points vs internal spans.

    One open question is the naming of this method, but I think start_trace is still accurate.

    @yurishkuro @michaelsembwever @bensigelman thoughts?

    opened by dkuebric 16
  • Span does not set error tag if `__exit__` is called with an exception

    Span does not set error tag if `__exit__` is called with an exception

    It seems like we'd want to do the following if a span is closed exceptionally, given that we're already logging the exception to the span:

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            self.set_tag(tags.ERROR, True)
        ...
    

    If others agree I can open a PR

    enhancement 
    opened by nziebart 13
  • expose OpenTracing and API versioning capabilities

    expose OpenTracing and API versioning capabilities

    Per https://github.com/opentracing/opentracing.github.io/issues/33 and https://github.com/opentracing/opentracing-go/pull/51#discussion_r52555910

    @bcronin @yurishkuro

    opened by bhs 13
  • Implement ScopeManager for in-process propagation (updated)

    Implement ScopeManager for in-process propagation (updated)

    This is an updated PR for supporting Span propagation, sitting on top of @palazzem's effort (#63), and updated with the feedback received there, as well as updated with the latest Java API.

    Summary of changes:

    • Tracer.start_manual was removed.
    • Tracer.start_span stays, without being deprecated, but now by default sets the active Span as the parent one, if any. This is a breaking change, and passing ignore_active_span=True will get the previous behavior.
    • Tracer.start_active became Tracer.start_active_span (longer, but better, more complete name overall).
    • finish_on_close became non-optional, being only left as optional in start_active_span as False (as all its parameters are optional, even operation_name).
    • Added Scope.manager, similar to Span.tracer (will also avoid duplicating code in the implementations).
    • Used properties for ScopeManager.active, Scope.span and others, to mimic what we have right now in the API (Span.context, Span.tracer, etc).

    Decided to iterate on a different PR as @palazzem seems to be very busy himself, and we will most likely iterate faster over here ;)

    Also, PR of the actual implementation code: https://github.com/opentracing/basictracer-python/pull/25

    Update 1: Mention that start_span() will implicitly use the active Span as the parent, if any.

    cc @yurishkuro @clutchski @wkiser @itsderek23

    opened by carlosalberto 9
  • ActiveSpanSource for in-process context propagation

    ActiveSpanSource for in-process context propagation

    Overview

    This PR aims to solve in-process context propagation for Python. It has three aims:

    • adding a simple, high level API that allows very simple tracing that will handle 90% of use cases
    • a low-level API that allows more control for tracing complex async operations
    • a pluggable interface that allows tracing async operations with all the common frameworks - threads, asyncio, tornado, etc

    Implementation details may be discussed here: https://github.com/opentracing/basictracer-python/pull/23

    We'd like to keep two types of feedback separate - nomenclature vs structural. If everybody agrees on the structure / semantics of the PR, then we can debate naming and nomenclature. Cool?

    Details

    The main idea is having one or more specific ActiveSpanSource implementations that keeps track of the current active Span so that the high-level API provides:

    with tracer.start_active_span("web.request") as span:
        span.set_tag("url", "/home")
        data = get_data()
    
        with tracer.start_active_span("template.render"):
            return "%s" % (data)
    

    When more control is needed, we can interact with the current active Span stack, see examples below:

    • Propagation when callbacks are used (link)
    • Fake server implementation that executes asynchronous jobs (link)
    • Propagation between different threads (link)
    • Propagation in asyncio (link)
    • Propagation in gevent (link)
    • Propagation in Tornado using a StackContext (link)

    Note: we might want to include these in an official OT library or helper module at some point ---^

    This API is flexible when handling some edge cases that could happen in simple or complex applications. Currently, asyncio, gevent, Tornado and threading are supported with specific implementations and with some helpers that may become part of some opentracing-contrib-* package.

    Nomenclature Questions

    • this assumes that we want to leave start_span untouched. In an ideal world, I think start_active_span should be the method used 99% of the time, so we should consider using something shorter / cleaner here like start_span, span or trace.

    What's missing and that will be completed after an initial review:

    • [ ] tests for the ActiveSpanSource API
    • [x] start_span must be a @deprecated function (at the moment it has been fully replaced by start_manual_span(...). or can we use start_span all the time?
    • [ ] instrument a real complex application before merging
    • [ ] update the documentation providing both manual and automatic propagation
    opened by palazzem 9
  • Add support for indicating if a global tracer has been registered

    Add support for indicating if a global tracer has been registered

    Adds support for knowing if a global tracer has been registered via a global variable including tests.

    NOTE: Python currently does not prevent you re-registering a global tracer multiple times like C# and Java does.

    opened by MikeGoldsmith 8
  • bring opentracing-python in sync with opentracing-go

    bring opentracing-python in sync with opentracing-go

    @yurishkuro can you take a look at this?

    I fixed the tests aside from the harness bound to zipkin_like... do we want to keep it around or not? If so, I can do the busywork of moving things around and renaming as needed.

    I'll highlight a few areas of interest or possible concern...

    opened by bhs 7
  • With gevent patch_all, jaeger has no span to show

    With gevent patch_all, jaeger has no span to show

    
    
    import os,sys
    g_proj_path = os.path.dirname(os.path.abspath(__file__))+"/../"
    sys.path.append(g_proj_path)
    sys.path.append(g_proj_path+"module/")
    sys.path.append(g_proj_path+"website/")
    from gevent import monkey
    monkey.patch_all()
    from opentracing.scope_managers.gevent import GeventScopeManager
    
    import logging
    import time
    from jaeger_client import Config
    
    if __name__ == "__main__":
        log_level = logging.DEBUG
        logging.getLogger('').handlers = []
        logging.basicConfig(format='%(asctime)s %(message)s', level=log_level)
    
        config = Config(
            config={ # usually read from some yaml config
                'sampler': {
                    'type': 'const',
                    'param': 1,
                },
                'logging': True,
            },
            service_name='te-app-name',
            validate=True,
            scope_manager=GeventScopeManager()
        )
        # this call also sets opentracing.tracer
        tracer = config.initialize_tracer()
        # gevent_opentracing.init_tracing(tracer)
        with tracer.start_span(operation_name='someWork') as span:
            span.set_tag("test","212")
    
        time.sleep(2)
    
    
    
    
    

    With gevent patch_all, jaeger has no span to show

    opened by chenbodeng719 0
  • docs: fix simple typo, corotuines -> coroutines

    docs: fix simple typo, corotuines -> coroutines

    There is a small typo in opentracing/scope_managers/tornado.py.

    Should read coroutines rather than corotuines.

    Semi-automated pull request generated by https://github.com/timgates42/meticulous/blob/master/docs/NOTE.md

    opened by timgates42 0
  • Replace @asyncio.coroutine with “async def” for Python 3.11

    Replace @asyncio.coroutine with “async def” for Python 3.11

    In the tests, replace the asyncio.coroutine decorator, which is deprecated since Python 3.8 and removed in 3.11, with “async def”.

    Because async functionality was not available in Python 2.7, and async def was introduced in 3.5, this does not appear to break the tests, except that flake8 in the Python 2.7 tox environment chokes on a SyntaxError for the two test files that are modified in this PR. I’m not sure what you would prefer to do about that.

    opened by musicinmybrain 0
  • setuptools warnings

    setuptools warnings

     * QA Notice: setuptools warnings detected:
     * 
     * 	Usage of dash-separated 'description-file' will not be supported in future versions. Please use the underscore name 'description_file' instead
     * 	Usage of dash-separated 'upload-dir' will not be supported in future versions. Please use the underscore name 'upload_dir' instead```
    opened by Alessandro-Barbieri 0
Owner
OpenTracing API
Consistent, expressive, vendor-neutral APIs for distributed tracing and context propagation
OpenTracing API
Simple Python API for the Ergo Platform Explorer

Ergo is a "Resilient Platform for Contractual Money." It is designed to be a platform for applications with the main focus to provide an efficient, se

null 7 Jul 6, 2021
An unofficial python API for trading on the DeGiro platform, with the ability to get real time data and historical data.

DegiroAPI An unofficial API for the trading platform Degiro written in Python with the ability to get real time data and historical data for products.

Jorrick Sleijster 5 Dec 16, 2022
OpenSea NFT API App using Python and Streamlit

opensea-nft-api-tutorial OpenSea NFT API App using Python and Streamlit Tutorial Video Walkthrough https://www.youtube.com/watch?v=49SupvcFC1M Instruc

null 64 Oct 28, 2022
Algorand Python API examples

Algorand-Py Algorand Python API examples This repo will hold example scripts to monitor activities on Algorand main net. You can: Monitor your assets

Karthik Dutt 2 Jan 23, 2022
A Python wrapper for Matrix Synapse admin API

Synapse-admin-api-python A Python wrapper for Matrix Synapse admin API. Versioning This library now supports up to Synapse 1.45.0, any Admin API intro

Knugi 9 Sep 28, 2022
A python API act as Control Center to control your Clevo Laptop via wmi on windows.

ClevoPyControlCenter A python API act as Control Center to control your Clevo Laptop via wmi on windows. Usage # pip3 install pymi from clevo_wmi impo

null 3 Sep 19, 2022
Simple and easy to use python API for the COVID registration booking system of the math department @ unipd (torre archimede)

Simple and easy to use python API for the COVID registration booking system of the math department @ unipd (torre archimede). This API creates an interface with the official browser, with more useful functionalities.

Guglielmo Camporese 4 Dec 24, 2021
A PG3D API Made with Python

PG3D Python API A Pixel Gun 3D Python API (Public Ver) Features Count: 29 How To Use? import api as pbn Examples pbn.isBanned(192819483) -> True pbn.f

Karim 2 Mar 24, 2022
A(Sync) Interface for internal Audible API written in pure Python.

Audible Audible is a Python low-level interface to communicate with the non-publicly Audible API. It enables Python developers to create there own Aud

mkb79 192 Jan 3, 2023
Python bindings for the Plex API.

Python-PlexAPI Overview Unofficial Python bindings for the Plex API. Our goal is to match all capabilities of the official Plex Web Client. A few of t

Michael Shepanski 931 Jan 7, 2023
An async API wrapper for Dress To Impress written in Python.

dti.py An async API wrapper for Dress To Impress written in Python. Some notes: For the time being, there are no front-facing docs for this beyond doc

Steve C 1 Dec 14, 2022
A pet facts python api

Pet-Facts-API A pet facts python api Project Links API :- https://pet-facts-api.vercel.app Docs :- https://fayasnoushad.github.io/Pet-Facts-API

Fayas Noushad 3 Dec 18, 2021
Python program that generates random user from API

RandomUserPy Author kirito sate #modules used requests, json, tkinter, PIL, urllib, io, install requests and PIL modules from pypi pip install Pillow

kiritosate 1 Jan 5, 2022
A Python wrapper API for operating and working with the Neo4j Graph Data Science (GDS) library

gdsclient NOTE: This is a work in progress and many GDS features are known to be missing or not working properly. This repo hosts the sources for gdsc

Neo4j 100 Dec 20, 2022
This repository provides a set of easy to understand and tested Python samples for using Acronis Cyber Platform API.

Base Acronis Cyber Platform API operations with Python !!! info Copyright © 2019-2021 Acronis International GmbH. This is distributed under MIT licens

Acronis International GmbH 3 Aug 11, 2022
Python client library for the Databento API

Databento Python Library The Databento Python client library provides access to the Databento API for both live and historical data, from applications

Databento, Inc. 35 Dec 24, 2022
Box CRUD API With Python

Box CRUD API: Consider a store which has an inventory of boxes which are all cuboid(which have length breadth and height). Each Cuboid has been added

Akhil Bhalerao 3 Feb 17, 2022
Iss-tracker - ISS tracking script in python using NASA's API

ISS Tracker Tracking International Space Station using NASA's API and plotting i

Partho 9 Nov 29, 2022
AIST++ API This repo contains starter code for using the AIST++ dataset.

AIST++ API This repo contains starter code for using the AIST++ dataset. To download the dataset or explore details of this dataset, please go to our

Google 260 Dec 30, 2022