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
Adds GraphQL support to your Flask application.

Flask-GraphQL Adds GraphQL support to your Flask application. Usage Just use the GraphQLView view from flask_graphql from flask import Flask from flas

GraphQL Python 1.3k Jan 03, 2023
Pagination support for flask

flask-paginate Pagination support for flask framework (study from will_paginate). It supports several css frameworks. It requires Python2.6+ as string

Lix Xu 264 Nov 07, 2022
A gRpc server like Flask (像Flask一样的gRpc服务)

Mask A gRpc server just like Flask. Install Mask support pypi packages, you can simply install by: pip install mask Document Mask manual could be fou

吴东 16 Jun 14, 2022
WebSocket support for Flask

flask-sock WebSocket support for Flask Installation pip install flask-sock Example from flask import Flask, render_template from flask_sock import Soc

Miguel Grinberg 165 Dec 27, 2022
Flask-app scaffold, generate flask restful backend

Flask-app scaffold, generate flask restful backend

jacksmile 1 Nov 24, 2021
Curso Desenvolvimento avançado Python com Flask e REST API

Curso Desenvolvimento avançado Python com Flask e REST API Curso da Digital Innovation One Professor: Rafael Galleani Conteudo do curso Introdução ao

Elizeu Barbosa Abreu 1 Nov 14, 2021
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
Free casino website. Madden just for learning / fun

Website Casino Free casino website. Madden just for learning / fun. Uses Jinja2 (HTML), Flask, JavaScript, etc. Dice game Preview

Kirill Zhosul 0 Jun 22, 2022
Simple flask api. Countdown to next train for each station in the subway system.

Simple flask api. Countdown to next train for each station in the subway system.

Kalyani Subbiah 0 Apr 17, 2022
REST API built using flask framework that used for managing bookmarks by individual users.

Bookmarks REST API REST API built using flask framework that used for managing bookmarks by individual users. API Consumers Note This app is built usi

Venkatesh Tantravahi 1 Dec 27, 2021
A simple barcode and QR code generator built in Python with Flask.

✨ Komi - Barcode & QR Generator ✨ A simple barcode and QR code generator built in Python with Flask. 📑 Table of Contents Usage Installation Contribut

Bonnie Fave 2 Nov 04, 2021
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
Rich implementation for Flask

Flask Rich Implements the Rich programming library with Flask. All features are toggleable, including: Better logging Colorful tracebacks Usage Import

BD103 13 Jun 06, 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
RestApi_flask_sql.alchemy - Product REST API With Flask & SQL Alchemy

REST API With Flask & SQL Alchemy Products API using Python Flask, SQL Alchemy and Marshmallow Quick Start Using Pipenv # Activate venv $ pipenv shell

amirwahla 1 Jan 01, 2022
Flask Multiple Database Login

Flask Multiple Database Login Handle login with flask using two diferent databases: UE | European; BR | Brazilian; These databases are separed to resp

Jose Pedro 1 Dec 16, 2021
Flask + marshmallow for beautiful APIs

Flask-Marshmallow Flask + marshmallow for beautiful APIs Flask-Marshmallow is a thin integration layer for Flask (a Python web framework) and marshmal

marshmallow-code 770 Jan 05, 2023
A flask template with Bootstrap 4, asset bundling+minification with webpack, starter templates, and registration/authentication. For use with cookiecutter.

cookiecutter-flask A Flask template for cookiecutter. (Supports Python ≥ 3.6) See this repo for an example project generated from the most recent vers

4.3k Dec 29, 2022