OpenTracing instrumentation for the Flask microframework

Overview

Flask-OpenTracing

This package enables distributed tracing in Flask applications via The OpenTracing Project. Once a production system contends with real concurrency or splits into many services, crucial (and formerly easy) tasks become difficult: user-facing latency optimization, root-cause analysis of backend errors, communication about distinct pieces of a now-distributed system, etc. Distributed tracing follows a request on its journey from inception to completion from mobile/browser all the way to the microservices.

As core services and libraries adopt OpenTracing, the application builder is no longer burdened with the task of adding basic tracing instrumentation to their own code. In this way, developers can build their applications with the tools they prefer and benefit from built-in tracing instrumentation. OpenTracing implementations exist for major distributed tracing systems and can be bound or swapped with a one-line configuration change.

If you want to learn more about the underlying python API, visit the python source code.

If you are migrating from the 0.x series, you may want to read the list of breaking changes.

Installation

Run the following command:

$ pip install Flask-Opentracing

Usage

This Flask extension allows for tracing of Flask apps using the OpenTracing API. All that it requires is for a FlaskTracing tracer to be initialized using an instance of an OpenTracing tracer. You can either trace all requests to your site, or use function decorators to trace certain individual requests.

Note: optional_args in both cases are any number of attributes (as strings) of flask.Request that you wish to set as tags on the created span

Initialize

FlaskTracing wraps the tracer instance that's supported by opentracing. To create a FlaskTracing object, you can either pass in a tracer object directly or a callable that returns the tracer object. For example:

import opentracing
from flask_opentracing import FlaskTracing

opentracing_tracer = ## some OpenTracing tracer implementation
tracing = FlaskTracing(opentracing_tracer, ...)

or

import opentracing
from flask_opentracing import FlaskTracing

def initialize_tracer():
    ...
    return opentracing_tracer

tracing = FlaskTracing(initialize_tracer, ...)

Trace All Requests

import opentracing
from flask_opentracing import FlaskTracing

app = Flask(__name__)

opentracing_tracer = ## some OpenTracing tracer implementation
tracing = FlaskTracing(opentracing_tracer, True, app, [optional_args])

Trace Individual Requests

import opentracing
from flask_opentracing import FlaskTracing

app = Flask(__name__)

opentracing_tracer = ## some OpenTracing tracer implementation
tracing = FlaskTracing(opentracing_tracer)

@app.route('/some_url')
@tracing.trace(optional_args)
def some_view_func():
    ...
    return some_view

Accessing Spans Manually

In order to access the span for a request, we've provided an method FlaskTracing.get_span(request) that returns the span for the request, if it is exists and is not finished. This can be used to log important events to the span, set tags, or create child spans to trace non-RPC events. If no request is passed in, the current request will be used.

Tracing an RPC

If you want to make an RPC and continue an existing trace, you can inject the current span into the RPC. For example, if making an http request, the following code will continue your trace across the wire:

@tracing.trace()
def some_view_func(request):
    new_request = some_http_request
    current_span = tracing.get_span(request)
    text_carrier = {}
    opentracing_tracer.inject(span, opentracing.Format.TEXT_MAP, text_carrier)
    for k, v in text_carrier.iteritems():
        new_request.add_header(k,v)
    ... # make request

Examples

See examples to view and run an example of two Flask applications with integrated OpenTracing tracers.

This tutorial has a step-by-step guide for using Flask-Opentracing with Jaeger.

Breaking changes from 0.x

Starting with the 1.0 version, a few changes have taken place from previous versions:

  • FlaskTracer has been renamed to FlaskTracing, although FlaskTracing can be used still as a deprecated name.
  • When passing an Application object at FlaskTracing creation time, trace_all_requests defaults to True.
  • When no opentracing.Tracer is provided, FlaskTracing will rely on the global tracer.

Further Information

If you're interested in learning more about the OpenTracing standard, please visit opentracing.io or join the mailing list. If you would like to implement OpenTracing in your project and need help, feel free to send us a note at [email protected].

Comments
  • Allow the underlying tracer initialization to be deferred

    Allow the underlying tracer initialization to be deferred

    For some tracer implementation (like Jaeger), the initialization of the tracer should be deferred until the wsgi server forks. We cannot directly create a jaeger tracer instance and pass it to FlaskTracer constructor upon startup.

    This PR addresses this issue. The underlying tracer initialization can be deferred if we pass a callable as argument to the constructor.

    opened by kevinjqiu 9
  • Question: Release Status

    Question: Release Status

    What is the status of getting master released? I need the broader opentracing version pinning in order to incorporate this library into my project, and master seems to work in my project, but I would much prefer to install a released package.

    opened by zrayn 8
  • Add default to _after_request_fn

    Add default to _after_request_fn

    If the flask app has a before_request method that shortcuts request execution, the request will not be present in the _current_spans dict but the tracer will still try to finish the span. Adding this default prevents an exception on those calls.

    opened by gravelg 8
  • opentracing setup version

    opentracing setup version

    Hi,

    opentracing 1.3 can run on python 3, 1.2 and below he can't.

    In the setup, 'opentracing>=1.1,<1.2' can be upgrade or there is some issue?

    Regards,

    opened by ChristopheDaSilva 6
  • Add deploy section into .travis.yml

    Add deploy section into .travis.yml

    opened by Jamim 4
  • Recursive tracing

    Recursive tracing

    We have a recursive route we're using for testing, when using the python aws xray tracing here: https://github.com/aws/aws-xray-sdk-python

    It includes all depth in the traces. However with the python-flask opentracing module, it's only showing one level of recursion in the waterfall graph. Is there anyway to get more detail?

    This is my config:

        config = jaeger_client.Config(config={'sampler': {'type': 'const', 'param': 1}, 
                                'logging': True,
                                'local_agent':
                                {'reporting_host': JAEGER_HOST}},
                        service_name="jaeger_opentracing_example")
        jaeger_tracer = config.initialize_tracer()
        # This will trace all requests; routes can also be chosen individually instead
        # Traced attributes lists additional request attributes we want to capture
        tracing = FlaskTracing(jaeger_tracer, True, app, traced_atrributes=['remote_addr'])
    

    Here is the flask route:

    @app.route("/recurse/<number>", methods=['GET'])
    def recurse(number):
        '''Recursively call itself with sleep'''
        num = int(number)
        if num> 0:
            val = num - 1
            sleep((randint(200,600)/1000.0))
            r = requests.get("http://turquoise.default/recurse/%d" % val)
            string = "%sAt depth %d!<br>" % (r.text, num)
            return string, r.status_code
        else:
            return "Depth 0!", 200
    

    For instance calling /recurse/7 results in the following waterfalls:

    Screenshot from 2019-05-15 08-54-34 Screenshot from 2019-05-15 08-54-43

    opened by r0fls 2
  • 'Tracer' object has no attribute '_noop_scope'

    'Tracer' object has no attribute '_noop_scope'

    Following the standard example but calling Flask.run explicitly, like:

           def initialize_tracer():
                from jaeger_client import Config
                config = Config(
                    config={
                        'sampler': {'type': 'const', 'param': 1}
                    },
                    service_name='hello-world')
                return config.initialize_tracer() # also sets opentracing.tracer                                      
    
            def rest_prediction_server():
                app = seldon_microservice.get_rest_microservice(
                    user_object, debug=DEBUG)
    
    
                from flask_opentracing import FlaskTracer
    
                flask_tracer = FlaskTracer(initialize_tracer, True, app)
    
                app.run(host='0.0.0.0', port=port)
    
    

    I get on REST call:

    Traceback (most recent call last):
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask/app.py", line 2292, in wsgi_app
        response = self.full_dispatch_request()
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1815, in full_dispatch_request
        rv = self.handle_user_exception(e)
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask_cors/extension.py", line 161, in wrapped_function
        return cors_after_request(app.make_response(f(*args, **kwargs)))
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1718, in handle_user_exception
        reraise(exc_type, exc_value, tb)
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask/_compat.py", line 35, in reraise
        raise value
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1811, in full_dispatch_request
        rv = self.preprocess_request()
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask/app.py", line 2087, in preprocess_request
        rv = func()
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask_opentracing/tracing.py", line 39, in start_trace
        self._before_request_fn(traced_attributes)
      File "/home/clive/anaconda3/lib/python3.6/site-packages/flask_opentracing/tracing.py", line 121, in _before_request_fn
        child_of=span_ctx)
      File "/home/clive/anaconda3/lib/python3.6/site-packages/opentracing/tracer.py", line 126, in start_active_span
        return self._noop_scope
    AttributeError: 'Tracer' object has no attribute '_noop_scope'
    
    

    Any ideas?

    opened by cliveseldon 2
  • add working examples with Jaeger tracer.

    add working examples with Jaeger tracer.

    I've added examples that use Jaeger instead of Lightstep, introduce serialization to the wire, and are bare minimal for ease of understanding.

    Before running both files you need to set JAEGER_HOST and WEBSERVER_HOST environment variables.

    @bhs @yurishkuro - please let me know if the approach seems good for you. I'll be writing README after your thumbs up on general direction.

    opened by ror6ax 2
  • does not work when started via flask run

    does not work when started via flask run

    The following script never starts the webserver and just hangs forever:

    import opentracing
    from flask import Flask
    from flask_opentracing import FlaskTracer
    from jaeger_client import Config
    
    app = Flask(__name__)
    
    config = Config(
    config={ # usually read from some yaml config
                              'sampler': {
                              'type': 'const',
                              'param': 1,
                      },
                              'logging': True,
                      },
    service_name='flask_app_1',
    )
    
    
    opentracing_tracer = config.initialize_tracer()
    tracer = FlaskTracer(opentracing_tracer)
    
    @app.route('/some_url')
    @tracer.trace()
    def some_view_func():
        return "some_view"
    

    With debug logging turned on, I see this:

    2017-11-06 15:10:16,177 Initializing Jaeger Tracer with UDP reporter

    Same script with minimal changes started by python scriptname.py works as a charm.

    opened by ror6ax 2
  • Remove non-ascii character from README.rst

    Remove non-ascii character from README.rst

    The python packaging doesn't declare itself to prefer utf-8, but the README contains it, so the long_description=open('README.rst').read() is failing with: UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 3800: ordinal not in range(128)

    There's only this single character, though, so just replace it with ASCII and solve the issue the easy way.

    opened by asilversempirical 2
  • Running flasktracing in gunicorn ends up to no root span

    Running flasktracing in gunicorn ends up to no root span

    I have an flask app using flasktracing, all the requests are shown in my jaeger backend UI without WSGI server. But when I run with gunicorn, I saw my "no-root-span" messages in the UI. I am using threads only with only one sync worker, and I have tried lazy-init and normal init the global tracer . Any idea how to fix it?

    opened by zh0uquan 1
  • Status code tag is not added to spans when using @trace decorator

    Status code tag is not added to spans when using @trace decorator

    How to reproduce the issue

    Let's use whatever tracer (I used jaeger for this example) and use the trace decorator. Ex.:

    from flask import Flask
    from flask_opentracing import FlaskTracer
    
    app = Flask(__name__)
    
    
    def initialize_tracer():
        config = Config(
            config={
                'sampler': {'type': 'const', 'param': 1}
            },
            service_name='hello-world'
        )
        return config.initialize_tracer()
    
    
    flask_tracer = FlaskTracer(initialize_tracer)
    
    
    @app.route('/')
    @flask_tracer.trace()
    def hello():
        return 'Hello'
    

    In the resulting span, the HTTP_STATUS_CODE tag should not be present.

    Possible cause

    Looking at the code, it seems the response is not being passed to the _after_request_fn method when using the trace decorator.

    https://github.com/opentracing-contrib/python-flask/blob/master/flask_opentracing/tracing.py#L83

    Possible fix

    I attached a PR with one possible solution.

    opened by Fortiz2305 0
  • scope_manager not accessable via opentracing

    scope_manager not accessable via opentracing

    In the opentracing API the scope manager is stored in the property _scope_manager and can be accessed as scope_manager. Currently, the property _scope_manager is not set in the FlaskTracing class. This leads to issues with opentracing instrumentation that needs access to it, e.g. the boto3 part of the https://github.com/uber-common/opentracing-python-instrumentation. (When I have more time, I will try to add a minimal example where failures actually occur.)

    I could imagine two fixes to this a) adding

    @property 
    def _scope_manager(self):
        return self.tracer._scopemanager
    

    or b) explicitly doing:

    self._scope_manager = self.tracer.scope_manager
    

    in the initializer. Happy to write the PR for this, if you provide me your preferences :slightly_smiling_face:

    opened by jendrikjoe 1
  • Extended traced_attributes scope to after_request (response) too

    Extended traced_attributes scope to after_request (response) too

    Traced_attributes is given and scanned in _after_request_fn too. The motivation is that this way the attributes of the response object could be presented in the trace also. Our goal is to have more control over the traced attributes because our task requires also the input and the output data in one point.

    opened by lukacsg 4
  • On 404 operation_id is None which causes issues with some tracers

    On 404 operation_id is None which causes issues with some tracers

    The wavefront tracer specifically requires span names be non-None:

    ValueError
    ValueError: Span name cannot be blank
    
    ValueError
    ValueError: Span name cannot be blank
    
    Traceback (most recent call last)
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
    File "/usr/local/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1952, in full_dispatch_request
    return self.finalize_request(rv)
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 1969, in finalize_request
    response = self.process_response(response)
    File "/usr/local/lib/python3.7/site-packages/flask/app.py", line 2266, in process_response
    response = handler(response)
    File "/usr/local/lib/python3.7/site-packages/flask_opentracing/tracing.py", line 43, in end_trace
    self._after_request_fn(response)
    File "/usr/local/lib/python3.7/site-packages/flask_opentracing/tracing.py", line 160, in _after_request_fn
    scope.close()
    File "/usr/local/lib/python3.7/site-packages/opentracing/scope_managers/__init__.py", line 77, in close
    self.span.finish()
    File "/usr/local/lib/python3.7/site-packages/wavefront_opentracing_sdk/span.py", line 165, in finish
    self._do_finish(time.time() - self.start_time)
    File "/usr/local/lib/python3.7/site-packages/wavefront_opentracing_sdk/span.py", line 191, in _do_finish
    self.tracer.report_span(self)
    File "/usr/local/lib/python3.7/site-packages/wavefront_opentracing_sdk/tracer.py", line 274, in report_span
    self._reporter.report(span)
    File "/usr/local/lib/python3.7/site-packages/wavefront_opentracing_sdk/reporting/composite.py", line 32, in report
    rep.report(wavefront_span)
    File "/usr/local/lib/python3.7/site-packages/wavefront_opentracing_sdk/reporting/console.py", line 35, in report
    default_source='unknown')
    File "/usr/local/lib/python3.7/site-packages/wavefront_sdk/common/utils.py", line 236, in tracing_span_to_line_data
    raise ValueError('Span name cannot be blank')
    ValueError: Span name cannot be blank
    

    Is there a mechanism to provide a default name for that case?

    opened by Alphasite 0
Releases(v1.0.0)
Owner
3rd-Party OpenTracing API Contributions
3rd-party contributions that use OpenTracing. **The repositories in this org are *not* affiliated with the CNCF.**
3rd-Party OpenTracing API Contributions
REST API with Flask and SQLAlchemy. I would rather not use it anymore.

Flask REST API Python 3.9.7 The Flask experience, without data persistence :D First, to install all dependencies: python -m pip install -r requirement

Luis Quiñones Requelme 1 Dec 15, 2021
A web application made with Flask that works with a weather service API to get the current weather from all over the world.

Weather App A web application made with Flask that works with a weather service API to get the current weather from all over the world. Uses data from

Christian Jairo Sarmiento 19 Dec 02, 2022
flask extension for integration with the awesome pydantic package

Flask-Pydantic Flask extension for integration of the awesome pydantic package with Flask. Installation python3 -m pip install Flask-Pydantic Basics U

248 Dec 26, 2022
Rate Limiting extension for Flask

Flask-Limiter Flask-Limiter provides rate limiting features to flask routes. It has support for a configurable backend for storage with current implem

Ali-Akber Saifee 922 Jan 08, 2023
Intranet de la Rez Flask web app

IntraRez Application Flask de l'Intranet de la Rez. Exigences Python : Probablement = 3.10 à terme, pour l'instant = 3.8 suffit ; Autres packages Li

3 Jul 03, 2022
A Python, Flask login system

Python Login System This is a basic login + authenticason system for flask using Flask_Login and Flask_SQLAlchemy Get started on your own To use this

MrShoe 0 Feb 02, 2022
SQLAlchemy database migrations for Flask applications using Alembic

Flask-Migrate Flask-Migrate is an extension that handles SQLAlchemy database migrations for Flask applications using Alembic. The database operations

Miguel Grinberg 2.2k Dec 28, 2022
Track requests to your Flask website with Matomo

Flask-Matomo Flask-Matomo is a library which lets you track the requests of your Flask website using Matomo (Piwik). Installation pip install flask-ma

Lucas Hild 13 Jul 14, 2022
Are-You-OK is a Flask-based, responsive Web App to monitor whether the Internet Service you care about is still working.

Are-You-OK Are-You-OK is a Flask-based, responsive Web App to monitor whether the Internet Service you care about is still working. Demo-Preview Get S

Tim Qiu 1 Oct 28, 2021
Flask Sitemapper is a small Python 3 package that generates XML sitemaps for Flask applications.

Flask Sitemapper Flask Sitemapper is a small Python 3 package that generates XML sitemaps for Flask applications. This allows you to create a nice and

6 Jan 06, 2023
PatientDB is a flask app to store patient information.

PatientDB PatientDB on Heroku "PatientDB is a simple web app that stores patient information, able to edit the information, and able to query the data

rbb 2 Jan 31, 2022
A team blog based on Flask

A team blog based on Flask This project isn't supported at the moment, please see a newer pypress-tornado Thanks for flask_website and newsmeme at [ht

老秋 549 Nov 10, 2022
Small flask based opds catalog designed to serve a directory via OPDS

teenyopds Small flask based opds catalog designed to serve a directory via OPDS, it has currently only been verified to work with KyBook 3 on iOS but

Adam Furbee 4 Jul 14, 2022
Adds Injector support to Flask.

Flask-Injector Adds Injector support to Flask, this way there's no need to use global Flask objects, which makes testing simpler. Injector is a depend

Alec Thomas 246 Dec 28, 2022
Brandnew-flask is a CLI tool used to generate a powerful and mordern flask-app that supports the production environment.

Brandnew-flask is still in the initial stage and needs to be updated and improved continuously. Everyone is welcome to maintain and improve this CLI.

brandonye 4 Jul 17, 2022
An python flask app with webserver example

python-flask-example-keepalive How it works? Basically its just a python flask webserver which can be used to keep any repl/herokuapp or any other ser

KangersHub 2 Sep 28, 2022
Control YouTube, streaming sites, media players on your computer using your phone as a remote.

Media Control Control Youtube, streaming sites, media players on your computer using your phone as a remote. Installation pip install -r requirements.

Shreyas Daniel 10 Dec 08, 2022
flask-apispec MIT flask-apispec (🥉24 · ⭐ 520) - Build and document REST APIs with Flask and apispec. MIT

flask-apispec flask-apispec is a lightweight tool for building REST APIs in Flask. flask-apispec uses webargs for request parsing, marshmallow for res

Joshua Carp 617 Dec 30, 2022
The Coodesh Python Backend Challenge (2021) written in Flask

Coodesh Back-end Challenge 🏅 2021 ID: 917 The Python Back-end Coodesh Challenge Description This API automatically retrieves users from the RandomUse

Marcus Vinicius Pereira 1 Oct 20, 2021
Telegram bot + Flask API ( Make Introduction pages )

Introduction-Page-Maker Setup the api Upload the flask api on your host Setup requirements Make pages file on your host and upload the css and js and

Plugin 9 Feb 11, 2022