Skip to content

Commit b0a4a22

Browse files
committed
[flask] rewrite Flask integration (#667)
* [flask] start work on new patch implementation * [flask] trace more internal parts * [flask] trace more things * [flask] round out prototype of new tracing * [flask] replace old patch.py with new_patch.py * [flask] finish up prototype and deduplicate some code * [flask] move wrapper helpers to wrappers.py * [flask] add docstrings * [flask] remove unused import * [flask] Update documentation * [flask] < 0.12.0 does not have Flask.finalize_request * [flask] handle status code as a string * [flask] use config API * [flask] update version parsing * [flask] patch signal receivers_for and add unpatch() * [flask] add test for Flask signals * [flask] fix patching/unpatching lists * [flask] use template name as span resource name * [flask] set test template directory * [flask] add test cases for Flask helpers * [flask] add test cases for Flask render functions * [flask] add test helpers to check if something is wrapped * [flask] simplify pin cloning logic * [flask] add blueprint tests * [flask] rename patch.py to monkey.py * [flask] make sure do to do Pin(tracer=self.tracer) in tests * [flask] fix spelling * [flask] add patch/unpatch idempotency tests * [flask] add initial support for v0.9 * [flask] update tests for v0.9 * [flask] use <= (0, 9) instead of == (0, 9) * [flask] fix signal names * [flask] add assertion message * [flask] skip send_from_directory * [flask] add find_span_by_name test helper * [flask] add error handler test cases * [flask] Use start_response instead of Flask.finalize_request for response code * [flask] assert bytes equality * [flask] add test caes for flask.views.View * [flask] enable by default * [flask] remove large TODO comment * [flask] change 404 resource name to '<method> 404' * [flask] fix py2 vs py3 base exception name * [flask] add request lifecycle tests * [flask] support unicode * [flask] rewrite Flask autopatch tests * [flask] run py27-flask09 tests in circleci * [flask] add static file tests * [flask] rename monkey.py back to patch.py * [flask] update docstring for flask being enabled by default * [flask] fix comments and docstrings * [flask] use ddtrace.utils.importlib.func_name * [core] modify Pin.get_from to accept multiple objects * [flask] fix remaining get_arg_or_kwargs * [flask] only use '<method> 404' if the endpoint is unknown * [flask] use request.path for http.URL tag * [flask] remove/fix TODOs * [flask] Add Pin.find(*objs) helper * [flask] only use 'def _wrap()' where necessary * [flask] mark 5xx errors by default, allow config of additional error codes * Update tests/contrib/flask/test_request.py Co-Authored-By: brettlangdon <[email protected]> * Update tests/contrib/flask/test_template.py Co-Authored-By: brettlangdon <[email protected]> * Update tests/contrib/flask/test_template.py Co-Authored-By: brettlangdon <[email protected]> * [flask] simplify fetching wrapped arg * [flask] fix spelling mistake * Update ddtrace/contrib/flask/patch.py Co-Authored-By: brettlangdon <[email protected]> * [flask] remove unnecessary 'func_name(func)' call * Update tests/contrib/flask/test_blueprint.py Co-Authored-By: brettlangdon <[email protected]> * [flask] fix remaining comments from kyle * [flask] fix flake8 issues * [flask] test distributed tracing * [flask] add find_span_parent helper * [flask] add hook test cases * [flask] fix spelling * [flask] rename Pin.find to Pin._find and add tests * [flask] one last Pin.find -> Pin._find * [flask] try to parse endpoint/url rule in Flask.preprocess_request as well * [flask] fix line too long issue
1 parent f4f8040 commit b0a4a22

25 files changed

+3195
-389
lines changed

.circleci/config.yml

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,14 @@ jobs:
329329
steps:
330330
- checkout
331331
- *restore_cache_step
332-
- run: tox -e 'flask_contrib{,_autopatch}-{py27,py34,py35,py36}-flask{010,011,012,10}-blinker' --result-json /tmp/flask.1.results
333-
- run: tox -e 'flask_cache_contrib-{py27,py34,py35,py36}-flask{010,011,012}-flaskcache{013}-memcached-redis{210}-blinker' --result-json /tmp/flask.2.results
334-
- run: tox -e 'flask_cache_contrib_autopatch-{py27,py34,py35,py36}-flask{010,011,012}-flaskcache{013}-memcached-redis{210}-blinker' --result-json /tmp/flask.3.results
335-
- run: tox -e 'flask_cache_contrib-{py27}-flask{010,011}-flaskcache{012}-memcached-redis{210}-blinker' --result-json /tmp/flask.4.results
336-
- run: tox -e 'flask_cache_contrib_autopatch-{py27}-flask{010,011}-flaskcache{012}-memcached-redis{210}-blinker' --result-json /tmp/flask.5.results
332+
- run: tox -e 'flask_contrib-{py27,py34,py35,py36}-flask{010,011,012,10}-blinker' --result-json /tmp/flask.1.results
333+
- run: TOX_SKIP_DIST=False tox -e 'flask_contrib_autopatch-{py27,py34,py35,py36}-flask{010,011,012,10}-blinker' --result-json /tmp/flask.2.results
334+
- run: tox -e 'flask_contrib-{py27}-flask{09}-blinker' --result-json /tmp/flask.3.results
335+
- run: TOX_SKIP_DIST=False tox -e 'flask_contrib_autopatch-{py27}-flask{09}-blinker' --result-json /tmp/flask.4.results
336+
- run: tox -e 'flask_cache_contrib-{py27,py34,py35,py36}-flask{010,011,012}-flaskcache{013}-memcached-redis{210}-blinker' --result-json /tmp/flask.5.results
337+
- run: TOX_SKIP_DIST=False tox -e 'flask_cache_contrib_autopatch-{py27,py34,py35,py36}-flask{010,011,012}-flaskcache{013}-memcached-redis{210}-blinker' --result-json /tmp/flask.6.results
338+
- run: tox -e 'flask_cache_contrib-{py27}-flask{010,011}-flaskcache{012}-memcached-redis{210}-blinker' --result-json /tmp/flask.7.results
339+
- run: TOX_SKIP_DIST=False tox -e 'flask_cache_contrib_autopatch-{py27}-flask{010,011}-flaskcache{012}-memcached-redis{210}-blinker' --result-json /tmp/flask.8.results
337340
- persist_to_workspace:
338341
root: /tmp
339342
paths:
@@ -342,6 +345,9 @@ jobs:
342345
- flask.3.results
343346
- flask.4.results
344347
- flask.5.results
348+
- flask.6.results
349+
- flask.7.results
350+
- flask.8.results
345351
- *save_cache_step
346352

347353
gevent:

ddtrace/contrib/flask/__init__.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,32 @@
11
"""
2-
The Flask trace middleware will track request timings and templates. It
3-
requires the `Blinker <https://pythonhosted.org/blinker/>`_ library, which
4-
Flask uses for signalling.
2+
The `Flask <http://flask.pocoo.org/>`_ integration will add tracing to all requests to your Flask application.
53
6-
To install the middleware, add::
4+
This integration will track the entire Flask lifecycle including user-defined endpoints, hooks,
5+
signals, and templating rendering.
76
8-
from ddtrace import tracer
9-
from ddtrace.contrib.flask import TraceMiddleware
7+
To configure tracing manually::
108
11-
and create a `TraceMiddleware` object::
9+
from ddtrace import patch_all
10+
patch_all()
1211
13-
traced_app = TraceMiddleware(app, tracer, service="my-flask-app", distributed_tracing=False)
12+
from flask import Flask
1413
15-
Here is the end result, in a sample app::
14+
app = Flask(__name__)
1615
17-
from flask import Flask
18-
import blinker as _
1916
20-
from ddtrace import tracer
21-
from ddtrace.contrib.flask import TraceMiddleware
17+
@app.route('/')
18+
def index():
19+
return 'hello world'
2220
23-
app = Flask(__name__)
2421
25-
traced_app = TraceMiddleware(app, tracer, service="my-flask-app", distributed_tracing=False)
22+
if __name__ == '__main__':
23+
app.run()
2624
27-
@app.route("/")
28-
def home():
29-
return "hello world"
3025
31-
Set `distributed_tracing=True` if this is called remotely from an instrumented application.
32-
We suggest to enable it only for internal services where headers are under your control.
26+
You may also enable Flask tracing automatically via ddtrace-run::
27+
28+
ddtrace-run python app.py
29+
3330
"""
3431

3532
from ...utils.importlib import require_modules
@@ -39,7 +36,11 @@ def home():
3936

4037
with require_modules(required_modules) as missing_modules:
4138
if not missing_modules:
39+
# DEV: We do this so we can `@mock.patch('ddtrace.contrib.flask._patch.<func>')` in tests
40+
from . import patch as _patch
4241
from .middleware import TraceMiddleware
43-
from .patch import patch
4442

45-
__all__ = ['TraceMiddleware', 'patch']
43+
patch = _patch.patch
44+
unpatch = _patch.unpatch
45+
46+
__all__ = ['TraceMiddleware', 'patch', 'unpatch']

ddtrace/contrib/flask/helpers.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from ddtrace import Pin
2+
import flask
3+
4+
5+
def get_current_app():
6+
"""Helper to get the flask.app.Flask from the current app context"""
7+
appctx = flask._app_ctx_stack.top
8+
if appctx:
9+
return appctx.app
10+
return None
11+
12+
13+
def with_instance_pin(func):
14+
"""Helper to wrap a function wrapper and ensure an enabled pin is available for the `instance`"""
15+
def wrapper(wrapped, instance, args, kwargs):
16+
pin = Pin._find(wrapped, instance, get_current_app())
17+
if not pin or not pin.enabled():
18+
return wrapped(*args, **kwargs)
19+
20+
return func(pin, wrapped, instance, args, kwargs)
21+
return wrapper
22+
23+
24+
def simple_tracer(name, span_type=None):
25+
"""Generate a simple tracer that wraps the function call with `with tracer.trace()`"""
26+
@with_instance_pin
27+
def wrapper(pin, wrapped, instance, args, kwargs):
28+
with pin.tracer.trace(name, service=pin.service, span_type=span_type):
29+
return wrapped(*args, **kwargs)
30+
return wrapper
31+
32+
33+
def get_current_span(pin, root=False):
34+
"""Helper to get the current span from the provided pins current call context"""
35+
if not pin or not pin.enabled():
36+
return None
37+
38+
ctx = pin.tracer.get_call_context()
39+
if not ctx:
40+
return None
41+
42+
if root:
43+
return ctx.get_current_root_span()
44+
return ctx.get_current_span()

ddtrace/contrib/flask/middleware.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from ... import compat
44
from ...ext import http, errors, AppTypes
55
from ...propagation.http import HTTPPropagator
6+
from ...utils.deprecation import deprecated
67

78
import flask.templating
89
from flask import g, request, signals
@@ -16,6 +17,7 @@
1617

1718
class TraceMiddleware(object):
1819

20+
@deprecated(message='Use patching instead (see the docs).', version='1.0.0')
1921
def __init__(self, app, tracer, service="flask", use_signals=True, distributed_tracing=False):
2022
self.app = app
2123
log.debug('flask: initializing trace middleware')

0 commit comments

Comments
 (0)