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
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
Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for your application.

Flask-Bcrypt Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for your application. Due to the recent increased prevelance of

Max Countryman 310 Dec 14, 2022
Flask-Rebar combines flask, marshmallow, and swagger for robust REST services.

Flask-Rebar Flask-Rebar combines flask, marshmallow, and swagger for robust REST services. Features Request and Response Validation - Flask-Rebar reli

PlanGrid 223 Dec 19, 2022
Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for your application.

Flask-Bcrypt Flask-Bcrypt is a Flask extension that provides bcrypt hashing utilities for your application. Due to the recent increased prevelance of

Max Countryman 282 Feb 11, 2021
Flask-Starter is a boilerplate starter template designed to help you quickstart your Flask web application development.

Flask-Starter Flask-Starter is a boilerplate starter template designed to help you quickstart your Flask web application development. It has all the r

Kundan Singh 259 Dec 26, 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
Flask Project Template A full feature Flask project template.

Flask Project Template A full feature Flask project template. See also Python-Project-Template for a lean, low dependency Python app. HOW TO USE THIS

Bruno Rocha 96 Dec 23, 2022
A Fast API style support for Flask. Gives you MyPy types with the flexibility of flask

Flask-Fastx Flask-Fastx is a Fast API style support for Flask. It Gives you MyPy types with the flexibility of flask. Compatibility Flask-Fastx requir

Tactful.ai 18 Nov 26, 2022
Flask-app scaffold, generate flask restful backend

Flask-app scaffold, generate flask restful backend

jacksmile 1 Nov 24, 2021
Flask pre-setup architecture. This can be used in any flask project for a faster and better project code structure.

Flask pre-setup architecture. This can be used in any flask project for a faster and better project code structure. All the required libraries are already installed easily to use in any big project.

Ajay kumar sharma 5 Jun 14, 2022
flask-reactize is a boostrap to serve any React JS application via a Python back-end, using Flask as web framework.

flask-reactize Purpose Developing a ReactJS application requires to use nodejs as back end server. What if you want to consume external APIs: how are

Julien Chomarat 4 Jan 11, 2022
Pf-flask-rest-com - Flask REST API Common Implementation by Problem Fighter Library

In the name of God, the Most Gracious, the Most Merciful. PF-Flask-Rest-Com Docu

Problem Fighter 3 Jan 15, 2022
Flask-Discord-Bot-Dashboard - A simple discord Bot dashboard created in Flask Python

Flask-Discord-Bot-Dashboard A simple discord Bot dashboard created in Flask Pyth

Ethan 8 Dec 22, 2022
Open-source Flask Sample built on top of flask-dance library

Open-source Flask Sample built on top of flask-dance library. The project implements the social login for Github and Twitter - Originally coded by TestDriven.IO.

App Generator 4 Jul 26, 2022
Flask-redmail - Email sending for Flask

Flask Red Mail: Email Sending for Flask Flask extension for Red Mail What is it?

Mikael Koli 11 Sep 23, 2022
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

null 6 Jan 6, 2023
Flask-template - A simple template for make an flask api

flask-template By GaGoU :3 a simple template for make an flask api notes: you ca

GaGoU 2 Feb 17, 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
Adds SQLAlchemy support to Flask

Flask-SQLAlchemy Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It aims to simplify using SQLAlchemy

The Pallets Projects 3.9k Dec 29, 2022