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
Serve angular production application from python flask backend. Quick and Easy

Serve angular production application from python flask backend. Quick and Easy

mark 1 Dec 01, 2022
A Flask web application that manages student entries in a SQL database

Student Database App This is a Flask web application that manages student entries in a SQL database. Users can upload a CSV into the SQL database, mak

rebecca 1 Oct 20, 2021
Guitar tabs web app for guitar fans, powered by Python/Flask

Guitar123 version 0.8.5 Guitar tabs web app for guitar fans, powered by Python/Flask Features Guitar tabs search and browse Easy to use for end user a

lowrain 48 Dec 27, 2022
This is a small notes web app, with python and flask microframework. Using sqlite3

Python Notes App. This is a small web application maked with flask-python for add notes easily and quickly. Dependencies. You can create a virtual env

Eduard 1 Dec 26, 2021
A web application that consists of a collection of board games

PyBoardGame About This website contains a collection of board games for users to enjoy, as well as various guides for the games. The web app is built

Larry Shi 0 Aug 11, 2021
A caching extension for Flask

Flask-Caching Adds easy cache support to Flask. This is a fork of the Flask-Cache extension. Flask-Caching also includes the cache module from werkzeu

Peter Justin 774 Jan 02, 2023
REST API with mongoDB and Flask.

Flask REST API with mongoDB py 3.10 First, to install all dependencies: python -m pip install -r requirements.txt Second, into the ./src/ folder, cop

Luis Quiñones Requelme 3 Mar 05, 2022
Python Flask API service, backed by DynamoDB, running on AWS Lambda using the traditional Serverless Framework.

Serverless Framework Python Flask API service backed by DynamoDB on AWS Python Flask API service, backed by DynamoDB, running on AWS Lambda using the

Andreu Jové 0 Apr 17, 2022
A tool for the game Politics And War. Saving players hours if searching for targets they can engage with.

A tool for the game Politics And War. Saving players hours if searching for targets they can engage with.

1 Dec 19, 2021
Lightweight library for providing filtering mechanism for your APIs using SQLAlchemy

sqlalchemy-filters-plus is a light-weight extendable library for filtering queries with sqlalchemy. Install pip install sqlalchemy-fitlers-plus Usage

Karami El Mehdi 38 Oct 05, 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
A weather report application build with Python, Flask, and Geopy.

A weather report application build with Python, Flask, and Geopy. Requirements Python 3

Brandon Wallace 6 May 07, 2022
Mixer -- Is a fixtures replacement. Supported Django, Flask, SqlAlchemy and custom python objects.

The Mixer is a helper to generate instances of Django or SQLAlchemy models. It's useful for testing and fixture replacement. Fast and convenient test-

Kirill Klenov 870 Jan 08, 2023
i18n and l10n support for Flask based on Babel and pytz

Flask Babel Implements i18n and l10n support for Flask. This is based on the Python babel module as well as pytz both of which are installed automatic

397 Dec 15, 2022
Flaskr: Intro to Flask, Test-Driven Development (TDD), and JavaScript

Flaskr - Intro to Flask, Test-Driven Development, and JavaScript Share on Twitter As many of you know, Flaskr -- a mini-blog-like-app -- is the app th

Michael Herman 2.2k Jan 04, 2023
A basic JSON-RPC implementation for your Flask-powered sites

Flask JSON-RPC A basic JSON-RPC implementation for your Flask-powered sites. Some reasons you might want to use: Simple, powerful, flexible and python

Cenobit Technologies 272 Jan 04, 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
A simple web application built using python flask. It can be used to scan SMEVai accounts for broken pages.

smescan A simple web application built using python flask. It can be used to scan SMEVai accounts for broken pages. Development Process Step 0: Clone

Abu Hurayra 1 Jan 30, 2022
Flask RESTful Web services using API to communicate between client and server.

Welcome! Open up two terminals, one for client and for server each Terminal 1 Terminal 2 Now navigate to the CW2_code directory in both like so $ cd C

Sehra Elahi 1 Nov 23, 2021
Boilerplate code for basic flask web apps

Flask Boilerplate This repository contains boilerplate code to start a project instantly It's mainly for projects which you plan to ship in less than

Abhishek 6 Sep 27, 2021