diff --git a/.travis.yml b/.travis.yml index 45eb60d..51678f6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,46 @@ +dist: xenial language: python python: - - "2.7" - - "3.6" -# command to install dependencies + - '2.7' + - '3.5' + - '3.6' + - '3.7' + - '3.8' + - 'nightly' + - 'pypy' + - 'pypy3.5' + +stages: + - lint + - test + - name: deploy + if: tag is present + +matrix: + include: + - stage: lint + name: flake8 + python: '3.7' + - stage: deploy + name: Deploy to PyPI + python: '3.7' + install: skip + script: skip + allow_failures: + - python: 'nightly' + install: - - pip install -r requirements.txt -# command to run tests + - pip install tox-travis + script: - - pytest + - tox + +deploy: + provider: pypi + distributions: sdist + user: '__token__' + password: + secure: 'YkwqdS/sPOcu43pPEAz3YvkJPp+FoSZ39xCM+3wXC5K+SpJW+APn5FH7izivs5DzqRtEP/dLwxUrtk4O+IvFxMgOeRXkGUszRUCYNV819iJwXlcVtODJfdjB+B/pFz6WERh13GhVl6PsG+SBgVE4GNu8F7svCepDX7kcHw1xa+tjkSg1mzmDuZuAhth8Gkph/7LHupx5VP8p96oOViLjI98SCnGDHbmzpDVzW1GZjO5wsTuCc6YYsc0ydKxMYUjaE+7PIMqziK3FjvZj6drnR8SV6e+9kWPNYQ8kodQTfeQuXqwq7s2Va/5lx9iZ+tdBCnCBEb+ehV43M13cqk6jnkZMbC2OZ7TY5Mtm/JWZe9QNcAutdeiqqaSCuGCWP7RT+jgyJaWtHGbAlRj8loHKCFgyzhwK77N/as9oyIxQSVYBIYNtSsvgG33BJQXO9oLf85qZ/C4WkFCX2JeZLAAvMv8Pm0r1lhmtN1nKEdOl6H0VQUls9S2u7s+HnEoTu9TXQ6FiDwV3iF9lZAr1kfPsGhZyEm/rxnMBrPnbbr7puLRJwOp5u1GT0nyp9C2Bpg1XrgpmrK14mpsd0eOMYIdEMc0v1qflze+DyVSEyccj/WbvXlW788bnoGd8svapRbo2ZUf+EfUaIi/S0dzNzQJMx7BHGprpuqHkEmmUbTO8o4Q=' + on: + condition: $TRAVIS_BUILD_STAGE_NAME = Deploy + tags: true diff --git a/Makefile b/Makefile index cb49486..9339e56 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,21 @@ +project := flask_opentracing + +pytest := PYTHONDONTWRITEBYTECODE=1 py.test --tb short -rxs \ + --cov-report term-missing:skip-covered --cov=$(project) tests + .PHONY: test publish install clean clean-build clean-pyc clean-test build upload-docs install: python setup.py install +check-virtual-env: + @echo virtual-env: $${VIRTUAL_ENV?"Please run in virtual-env"} + +bootstrap: check-virtual-env + pip install -r requirements.txt + pip install -r requirements-test.txt + python setup.py develop + clean: clean-build clean-pyc clean-test clean-build: @@ -25,13 +38,18 @@ clean-test: rm -f coverage.xml rm -fr htmlcov/ -test: - python setup.py test - make -C docs doctest +lint: + flake8 $(project) tests + +test: + $(pytest) build: python setup.py build +docs: + make -C docs doctest + upload-docs: git submodule update --init # for sphinx flask theme python setup.py build_sphinx diff --git a/README.rst b/README.rst index 8e5db8f..521208d 100644 --- a/README.rst +++ b/README.rst @@ -2,14 +2,22 @@ Flask-OpenTracing ################# +**Note:** *This package is no longer maintained or supported. Migrate your application +to use* `opentelemetry-api`_. + + 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`_. + .. _The OpenTracing Project: http://opentracing.io/ .. _source code: https://github.com/opentracing/opentracing-python +.. _breaking changes: #breaking-changes-from-0-x +.. _opentelemetry-api: https://pypi.org/project/opentelemetry-api/ Installation ============ @@ -24,7 +32,7 @@ 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 +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 @@ -32,28 +40,28 @@ instance of an OpenTracing tracer. You can either trace all requests to your sit Initialize ---------- -`FlaskTracer` wraps the tracer instance that's supported by opentracing. To create a `FlaskTracer` object, you can either pass in a tracer object directly or a callable that returns the tracer object. For example: +`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: .. code-block:: python import opentracing - from flask_opentracing import FlaskTracer + from flask_opentracing import FlaskTracing opentracing_tracer = ## some OpenTracing tracer implementation - tracer = FlaskTracer(opentracing_tracer, ...) + tracing = FlaskTracing(opentracing_tracer, ...) or .. code-block:: python import opentracing - from flask_opentracing import FlaskTracer + from flask_opentracing import FlaskTracing def initialize_tracer(): ... return opentracing_tracer - tracer = FlaskTracer(initialize_tracer, ...) + tracing = FlaskTracing(initialize_tracer, ...) Trace All Requests @@ -62,12 +70,12 @@ Trace All Requests .. code-block:: python import opentracing - from flask_opentracing import FlaskTracer + from flask_opentracing import FlaskTracing app = Flask(__name__) opentracing_tracer = ## some OpenTracing tracer implementation - tracer = FlaskTracer(opentracing_tracer, True, app, [optional_args]) + tracing = FlaskTracing(opentracing_tracer, True, app, [optional_args]) Trace Individual Requests ------------------------- @@ -75,15 +83,15 @@ Trace Individual Requests .. code-block:: python import opentracing - from flask_opentracing import FlaskTracer + from flask_opentracing import FlaskTracing app = Flask(__name__) opentracing_tracer = ## some OpenTracing tracer implementation - tracer = FlaskTracer(opentracing_tracer) + tracing = FlaskTracing(opentracing_tracer) @app.route('/some_url') - @tracer.trace(optional_args) + @tracing.trace(optional_args) def some_view_func(): ... return some_view @@ -91,7 +99,7 @@ Trace Individual Requests Accessing Spans Manually ------------------------ -In order to access the span for a request, we've provided an method `FlaskTracer.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. +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 -------------- @@ -100,14 +108,14 @@ If you want to make an RPC and continue an existing trace, you can inject the cu .. code-block:: python - @tracer.trace() + @tracing.trace() def some_view_func(request): new_request = some_http_request - current_span = tracer.get_span(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(): - request.add_header(k,v) + new_request.add_header(k,v) ... # make request Examples @@ -120,6 +128,18 @@ 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 =================== diff --git a/VERSION b/VERSION index 0ea3a94..227cea2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 +2.0.0 diff --git a/docs/index.rst b/docs/index.rst index 87eaed0..1401be7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -34,12 +34,12 @@ Trace All Requests .. code-block:: python import opentracing - from flask_opentracing import FlaskTracer + from flask_opentracing import FlaskTracing app = Flask(__name__) opentracing_tracer = ## some OpenTracing tracer implementation - tracer = FlaskTracer(opentracing_tracer, True, app, [optional_args]) + tracing = FlaskTracing(opentracing_tracer, True, app, [optional_args]) Trace Individual Requests ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -47,15 +47,15 @@ Trace Individual Requests .. code-block:: python import opentracing - from flask_opentracing import FlaskTracer + from flask_opentracing import FlaskTracing app = Flask(__name__) opentracing_tracer = ## some OpenTracing tracer implementation - tracer = FlaskTracer(opentracing_tracer) + tracing = FlaskTracing(opentracing_tracer) @app.route('/some_url') - @tracer.trace(optional_args) + @tracing.trace(optional_args) def some_view_func(): ... return some_view @@ -63,7 +63,7 @@ Trace Individual Requests Accessing Spans Manually ~~~~~~~~~~~~~~~~~~~~~~~~ -In order to access the span for a request, we've provided an method `FlaskTracer.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. +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 ~~~~~~~~~~~~~~ @@ -72,10 +72,10 @@ If you want to make an RPC and continue an existing trace, you can inject the cu .. code-block:: python - @tracer.trace() + @tracing.trace() def some_view_func(request): new_request = some_http_request - current_span = tracer.get_span(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(): diff --git a/example/README.md b/example/README.md index 537f1d2..cb716fd 100644 --- a/example/README.md +++ b/example/README.md @@ -1,74 +1,86 @@ ## Example -This example has a flask client and server and shows they can trace requests -both within and between the two sites using the flask_opentracing extension and +This example has a flask client and server and shows how to trace the requests +to a webserver from a client using the flask_opentracing extension and a concrete variant of an OpenTracing tracer. To run, make sure that you run pip -install for flask, opentracing, and lightstep (although you will not be able to -view the lightstep traces unless you have an access token). +install for flask, opentracing, jaeger_client. -Open two terminals and navigate to this directory. Run the following commands in +### Set up Jaeger: + +First, we'll have to download and run our Jaeger instance. It collects and displays +traces in neat graphical format. + +If you already have Docker installed, run this: + +``` +docker run -d -e \ + COLLECTOR_ZIPKIN_HTTP_PORT=9411 \ + -p 5775:5775/udp \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 14268:14268 \ + -p 9411:9411 \ + jaegertracing/all-in-one:latest +``` + +You should be able to see a web interface by browsing to http://localhost:16686/search + +![traced request](https://raw.githubusercontent.com/opentracing-contrib/python-flask/example/example/img/jaeger_0.png) + +Now, open two terminals and navigate to this directory. Run the following commands in each terminal: First window: -``` -> export FLASK_APP=server.py -> flask run +``` +> python3 server.py ``` -Second window: +Give it few seconds to start and run this in second window: -``` -> export FLASK_APP=client.py -> flask run --port=0 +``` +> python3 client.py ``` -To test the traces, check what port the client is running on and load -localhost:port. If you have a lightstep tracer token, you -should be able to view the trace data on the lightstep interface. -(NOTE: if you wish to use a different OpenTracing tracer, simply replace -`ls_tracer` with the OpenTracing tracer instance of your choice.) +To see the traced requests, go to Jaeger web interface and refresh the page. +Select your service name from dropdown on the left (it's +"jaeger_opentracing_example" in our case) and press Find traces button at the bottom of the page. -### Trace a request: -Navigate to `/request/simple/` where numrequests is the number -of requests you want to send to the page. This will send a request to the server -app, and the trace will include a span on both the client and server sides. This -occurs automatically since both the client and server functions are decorated -with @tracer.trace(). +![traced request](https://raw.githubusercontent.com/opentracing-contrib/python-flask/example/example/img/jaeger.png) -Result for `/request/simple/1`: -![request and response spans](https://raw.githubusercontent.com/kcamenzind/flask_opentracing/master/example/img/simple.png) +(NOTE: if you wish to use a different OpenTracing tracer instead of Jaeger, simply replace +`jaeger_tracer` with the OpenTracing tracer instance of your choice.) -### Log something to a request: +### Trace a request from browser: -Navigate to `/log`. This will log a message to the server-side span. The client -span is created automatically through the @tracer.trace() decorator, while the -logging occurs within the function by accessing the current span as follows: +Browse to http://localhost:5000/log and compare the trace in Jaeger. +The last one has 2 spans instead of 3. The span of webserver's GET method is missing. +That is because client.py starts a trace and passes trace context over the wire, whereas the request from your browser has no tracing context in it. -```python -span = tracer.get_span() -span.log_event("hello world") -``` -Result for `/log`: +### Add spans to the trace manually: -![span with log](https://raw.githubusercontent.com/kcamenzind/flask_opentracing/master/example/img/log.png) +In log function of the server app, we are creating current_span. This is done to +trace the work that is being done to render the response to /log endpoint. Suppose there's +a database connection happening. By creating a separate span for it, you'll be able +to trace the DB request separately from rendering or the response. This gives a +lot of flexibility to the user. -### Add spans to the trace manually: +Speaking about databases, using install_all_patches() method from +opentracing_instrumentation package gives you a way to trace +your MySQLdb, SQLAlchemy, Redis queries without writing boilerplate code. -Navigate to `/request/childspan/`. This will send a request to -the server app, and the trace will include a span on both the client and server -sides. The server app will additionally create a child span for the server-side -span. This example demonstrates how you can trace non-RPC function calls in your -app, through accessing the current span as follows: +Following code shows how to create a span based on already existing one. ```python -parent_span = tracer.get_span() -child_span = ls_tracer.start_span("inside create_child_span", parent_span) +# child_scope.span automatically inherits from the Span created +# for this request by the Flask instrumentation. +child_scope = jaeger_tracer.start_active_span('inside create_child_span') ... do some stuff -child_span.finish() +child_scope.close() ``` -Result for `/request/childspan/2`: -![two child spans](https://raw.githubusercontent.com/kcamenzind/flask_opentracing/master/example/img/childspan.png) +![traced request](https://raw.githubusercontent.com/opentracing-contrib/python-flask/example/example/img/jaeger_1.png) diff --git a/example/client.py b/example/client.py index 10bb7d5..14b8713 100644 --- a/example/client.py +++ b/example/client.py @@ -1,66 +1,34 @@ -from flask import Flask -from flask_opentracing import FlaskTracer -import lightstep.tracer -import opentracing -import urllib2 - -app = Flask(__name__) - -# one-time tracer initialization code -ls_tracer = lightstep.tracer.init_tracer(group_name="example client", access_token="{your_lightstep_token}") -# this tracer does not trace all requests, so the @tracer.trace() decorator must be used -tracer = FlaskTracer(ls_tracer) - -@app.route("/") -def index(): - ''' - Index page, has no tracing. - ''' - return "Index Page" - - -@app.route("/request/